Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add sharding to the endpoint #13

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 43 additions & 26 deletions server.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,37 +35,31 @@ def db_init():
conn = get_db()
c = conn.cursor()
c.execute('''CREATE TABLE IF NOT EXISTS store
(key TEXT PRIMARY KEY, value TEXT, created DATEIME, updated DATETIME)''')
(shard TEXT, key TEXT, value TEXT, created DATEIME, updated DATETIME, PRIMARY KEY(shard, key))''')
conn.commit()

# DB Access

def db_store(key, value):
def db_store(shard, key, value):
value = json.dumps(value)
conn = get_db()
c = conn.cursor()
try:
c.execute('''INSERT INTO store
VALUES (?, ?, strftime('%Y-%m-%d %H:%M:%S', 'now'), strftime('%Y-%m-%d %H:%M:%S', 'now'))
''', (key, value))
except sqlite3.IntegrityError:
c.execute('''UPDATE store
SET value = ?, updated = strftime('%Y-%m-%d %H:%M:%S', 'now')
WHERE key = ?
''', (value, key))
c.execute('''INSERT OR REPLACE INTO store
VALUES (?, ?, ?, strftime('%Y-%m-%d %H:%M:%S', 'now'), strftime('%Y-%m-%d %H:%M:%S', 'now'))
''', (shard, key, value))
conn.commit()

def db_fetch(key):
def db_fetch(shard, key):
c = get_db().cursor()
c.execute('SELECT * FROM store WHERE key = ? LIMIT 1', (key,))
c.execute('SELECT * FROM store WHERE shard = ? AND key = ? LIMIT 1', (shard, key))
result = c.fetchone()
if result:
value = result['value']
return json.loads(value)
else:
return None

def db_delete(key: str) -> bool:
def db_delete(shard: str, key: str) -> bool:
conn = get_db()
c = conn.cursor()
c.execute('DELETE FROM store WHERE key = ?', (key,))
Expand Down Expand Up @@ -115,8 +109,8 @@ def not_found(*_, message=None):
def internal_server_error(*_, message=None):
return json_error(500, message or "internal server error")

@app.route('/<path:key>', methods=['POST'])
def store(key):
@app.route('/<shard>/<path:key>', methods=['POST'])
def store(shard, key):
'''
Store a value
---
Expand All @@ -125,11 +119,17 @@ def store(key):
consumes:
- application/json
parameters:
- in: path
name: shard
type: string
required: true
default: 'group1'
description: a code used consistently for every request from your app.
- in: path
name: key
type: string
required: true
default: 'group1/users/georgina'
default: 'users/georgina'
description: the key you want to store the value at
- in: body
name: value
Expand All @@ -142,15 +142,18 @@ def store(key):
food: marzipan
responses:
400:
description: The given value was invalid.
description: The given shard or value was invalid.
200:
description: The key was successfully stored.
schema:
type: object
properties:
shard:
type: string
example: group1
key:
type: string
example: group1/users/georgina
example: users/georgina
value:
type: string
example: {"name": "georgina", "food": "marzipan"}
Expand All @@ -163,23 +166,30 @@ def store(key):
return bad_request(message="You need to supply JSON")

try:
db_store(key, value)
db_store(shard, key, value)
except:
return internal_server_error(message="Failed to store key")

return jsonify({
'shard': shard,
'key': key,
'value': value,
})

@app.route('/<path:key>', methods=['DELETE'])
def delete(key):
@app.route('/<shard>/<path:key>', methods=['DELETE'])
def delete(shard, key):
'''
Delete a key (if it exists)
---
tags:
- delete
parameters:
- in: path
name: shard
type: string
required: true
default: 'group1'
description: a code used consistently for every request from your app.
- in: path
name: key
type: string
Expand All @@ -193,22 +203,29 @@ def delete(key):
description: The key did not exist
'''

if db_delete(key):
if db_delete(shard, key):
return jsonify({
'shard': shard,
'key': key,
'deleted': True,
})
else:
return not_found(message="No such key: %s" % (key,))

@app.route('/<path:key>', methods=['GET'])
def fetch(key):
@app.route('/<shard>/<path:key>', methods=['GET'])
def fetch(shard, key):
'''
Retrieve a value
---
tags:
- fetch
parameters:
- in: path
name: shard
type: string
required: true
default: 'group1'
description: a code used consistently for every request from your app.
- in: path
name: key
type: string
Expand All @@ -227,7 +244,7 @@ def fetch(key):
food: marzipan
'''

result = db_fetch(key)
result = db_fetch(shard, key)
if result is None:
return not_found(message="No such key: %s" % (key,))

Expand Down
16 changes: 11 additions & 5 deletions server_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,9 @@ def test_flow(self):
"truthy": True,
"a string": "yes, a string 🎉",
}
KEY = "my/test/key1"
PATH = f"/{KEY}"
SHARD = "my"
KEY = "test/key1"
PATH = f"/{SHARD}/{KEY}"

# it should not previously exist
res = self.client.get(PATH)
Expand All @@ -43,6 +44,7 @@ def test_flow(self):
self.assertEqual(res.status_code, 200)
res_json = res.get_json()
self.assertEqual(res_json["value"]["one"], 1)
self.assertEqual(res_json["shard"], SHARD)
self.assertEqual(res_json["key"], KEY)

# now it should exist
Expand All @@ -55,7 +57,7 @@ def test_flow(self):
res = self.client.delete(PATH)
self.assertEqual(res.status_code, 200)
res_json = res.get_json()
self.assertEqual(res_json, {'key': KEY, 'deleted': True})
self.assertEqual(res_json, {'shard': SHARD, 'key': KEY, 'deleted': True})

# now it should no longer exist
res = self.client.get(PATH)
Expand All @@ -66,8 +68,9 @@ def test_flow(self):

def test_updates(self):
data = {"number": 1}
KEY = "my/test/key2"
PATH = f"/{KEY}"
SHARD = "my"
KEY = "test/key2"
PATH = f"/{SHARD}/{KEY}"

# check that it doesn't exist
res = self.client.get(PATH)
Expand Down Expand Up @@ -108,6 +111,9 @@ def test_invalid_input(self):
# the base url is not allowed to be posted to
res = self.client.post("/", json={"hello": True})
self.assertEqual(res.status_code, 405)
# the base url is not allowed to be posted to
res = self.client.post("/tes", json={"hello": True})
self.assertEqual(res.status_code, 404)

def test_api_spec_endpoints(self):
res = self.client.get("/api/spec")
Expand Down