diff --git a/server.py b/server.py index 6d1a4d6..36921ac 100755 --- a/server.py +++ b/server.py @@ -35,29 +35,23 @@ 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'] @@ -65,7 +59,7 @@ def db_fetch(key): 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,)) @@ -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('/', methods=['POST']) -def store(key): +@app.route('//', methods=['POST']) +def store(shard, key): ''' Store a value --- @@ -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 @@ -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"} @@ -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('/', methods=['DELETE']) -def delete(key): +@app.route('//', 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 @@ -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('/', methods=['GET']) -def fetch(key): +@app.route('//', 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 @@ -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,)) diff --git a/server_test.py b/server_test.py index c33cc2c..312b7b7 100644 --- a/server_test.py +++ b/server_test.py @@ -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) @@ -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 @@ -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) @@ -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) @@ -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")