diff --git a/server/README.md b/server/README.md index c04531d..727cadc 100644 --- a/server/README.md +++ b/server/README.md @@ -1 +1 @@ -# inventory-prototype server +# inventory server diff --git a/server/requirements.txt b/server/requirements.txt index d0f5492..7af1d86 100644 --- a/server/requirements.txt +++ b/server/requirements.txt @@ -1,6 +1,8 @@ flask==3.0.3 -pre-commit==3.8.0 +flask-cors==5.0.0 +pre-commit~=3.5.0 python-dotenv==1.0.0 -sphinx==7.4.7 +sphinx~=7.1.2 sphinx-rtd-theme==2.0.0 werkzeug==3.0.3 +pytest==8.3.2 \ No newline at end of file diff --git a/server/src/common.py b/server/src/common.py index 2cf57f9..fc5b3e2 100644 --- a/server/src/common.py +++ b/server/src/common.py @@ -3,6 +3,7 @@ import sqlite3 import time import typing +from typing import List from typing import Union import flask @@ -49,7 +50,7 @@ def time_ms() -> int: return int(time.time_ns() * 1e-6) -def create_response(code: int, body: Union[list[dict], dict]) -> dict: +def create_response(code: int, body: Union[List[dict], dict]) -> dict: if not isinstance(body, list): body = [body] return { diff --git a/server/src/db.py b/server/src/db.py index 181b643..5fc49a4 100644 --- a/server/src/db.py +++ b/server/src/db.py @@ -1,6 +1,7 @@ # TODO documentation from sqlite3.dbapi2 import Connection from sqlite3.dbapi2 import Cursor +from typing import List from typing import Type import auth @@ -22,7 +23,7 @@ def get(id_: str, entity_type: Type[models.Model]): return entity.to_response() -def update(entity_type: Type[models.Model], immutable_props: list[str]): +def update(entity_type: Type[models.Model], immutable_props: List[str]): # TODO documentation form = common.FlaskPOSTForm(flask.request.form) conn, cursor = common.get_db_connection() @@ -83,7 +84,6 @@ def remove(entity_type: Type[models.Model]): def list_(entity_type: Type[models.Model]): # TODO documentation - # TODO add a search functionality option to this too conn, cursor = common.get_db_connection() # If GET use query parameters, else if POST use form data diff --git a/server/src/models.py b/server/src/models.py index b952cf3..bf36383 100644 --- a/server/src/models.py +++ b/server/src/models.py @@ -2,6 +2,7 @@ import time from typing import Any from typing import Dict +from typing import List from typing import Optional from typing import Type @@ -10,11 +11,11 @@ class Model: - def to_dict(self) -> dict[str, Any]: + def to_dict(self) -> Dict[str, Any]: # TODO documentation return vars(self) - def to_response(self) -> dict[str, Any]: + def to_response(self) -> Dict[str, Any]: # TODO documentation return common.create_response(200, self.to_dict()) @@ -130,13 +131,13 @@ def __hash__(self): class EntityCache: def __init__(self): - self._map: Dict[EntityCacheKey, list[Model]] = {} + self._map: Dict[EntityCacheKey, List[Model]] = {} - def add(self, key: EntityCacheKey, entities: list[Model]) -> None: + def add(self, key: EntityCacheKey, entities: List[Model]) -> None: if len(entities) > 0: self._map[key] = entities - def get(self, key: EntityCacheKey) -> Optional[list[Model]]: + def get(self, key: EntityCacheKey) -> Optional[List[Model]]: for k in self._map.copy(): if k.expiry_time > time.time(): self._map.pop(k) @@ -146,13 +147,13 @@ def get(self, key: EntityCacheKey) -> Optional[list[Model]]: return None @staticmethod - def cut(entities: list[Model], limit: int, offset: int): + def cut(entities: List[Model], limit: int, offset: int): start_idx = max(min(offset, len(entities)), 0) end_idx = min(offset + limit, len(entities)) return entities[start_idx:end_idx] -def get_entity_parameters(entity_type: Type[Model]) -> dict[str, Any]: +def get_entity_parameters(entity_type: Type[Model]) -> Dict[str, Any]: raw_params = inspect.signature(entity_type.__init__).parameters dict_params = dict(raw_params) dict_params.pop('self') diff --git a/server/src/wsgi.py b/server/src/wsgi.py index 70a6e25..bb5175d 100644 --- a/server/src/wsgi.py +++ b/server/src/wsgi.py @@ -8,8 +8,9 @@ from api_item_routes import api_item_blueprint from api_reservation_routes import api_reservation_blueprint from api_user_routes import api_user_blueprint -from werkzeug.exceptions import HTTPException from flask_cors import CORS +from werkzeug.exceptions import HTTPException + def _create_table(entity_type: Type[models.Model]): model_keys = models.get_entity_parameters(entity_type).items() diff --git a/server/tst/test_api_box_routes.py b/server/tst/test_api_box_routes.py index d8c9c0d..41c20eb 100644 --- a/server/tst/test_api_box_routes.py +++ b/server/tst/test_api_box_routes.py @@ -1,5 +1,6 @@ import json import unittest +from typing import Dict from typing import Optional import auth @@ -45,7 +46,7 @@ def test_400_duplicate_name(self): self.call_route_assert_code(200, attrs) self.call_route_assert_code(400, attrs) - def call_route(self, attrs: Optional[dict[str, str]] = None): + def call_route(self, attrs: Optional[Dict[str, str]] = None): return self.client.post('/api/box/create', data=attrs) @@ -73,7 +74,7 @@ def test_200(self): }, ) - def call_route(self, attrs: dict[str, str]): + def call_route(self, attrs: Dict[str, str]): return self.client.post('/api/box/get', data=attrs) @@ -96,7 +97,7 @@ def test_400_no_update_properties(self): def test_400_malformed_update_properties(self): pass - def call_route(self, attrs: dict[str, str]): + def call_route(self, attrs: Dict[str, str]): return self.client.post('/api/box/update', data=attrs) @@ -113,7 +114,7 @@ def test_200_without_verification(self): def test_500_duplicate_id(self): pass - def call_route(self, attrs: dict[str, str]): + def call_route(self, attrs: Dict[str, str]): return self.client.post('/api/box/remove', data=attrs) @@ -136,7 +137,7 @@ def test_400_sortby_malformed(self): def test_400_invalid_sort_key(self): pass - def call_route(self, attrs: dict[str, str]): + def call_route(self, attrs: Dict[str, str]): return self.client.post('/api/boxes/list', data=attrs) diff --git a/server/tst/tstutil.py b/server/tst/tstutil.py index b42d994..abc7662 100644 --- a/server/tst/tstutil.py +++ b/server/tst/tstutil.py @@ -3,6 +3,7 @@ import sqlite3 import unittest from typing import Any +from typing import Dict from typing import Optional from typing import Type @@ -27,7 +28,7 @@ def tearDown(self): self.ctx.pop() # Drop tables in setUp so output is preserved for analysis - def call_route_assert_code(self, status_code: int, attrs: Optional[dict[str, Any]] = None, err_msg: str = None): + def call_route_assert_code(self, status_code: int, attrs: Optional[Dict[str, Any]] = None, err_msg: str = None): resp = self.call_route(attrs) self.assertEqual(status_code, resp.status_code, resp.data) resp_json = json.loads(resp.data) @@ -42,7 +43,7 @@ def call_route_assert_code(self, status_code: int, attrs: Optional[dict[str, Any self.assertEqual(err_msg, err_json['description']) return resp_json - def assert_single_entity(self, resp_json, expected: dict[str, str]): + def assert_single_entity(self, resp_json, expected: Dict[str, str]): self.assertIn('body', resp_json) self.assertIsNotNone(resp_json['body']) self.assertEqual(1, len(resp_json['body'])) @@ -56,7 +57,7 @@ def assert_single_entity(self, resp_json, expected: dict[str, str]): def test_200(self): raise NotImplementedError() - def call_route(self, attrs: dict[str, str]): + def call_route(self, attrs: Dict[str, str]): raise NotImplementedError() @@ -78,7 +79,7 @@ def test_401_user_not_found(self): self.call_route_assert_code(401, attrs, 'User was not found') def test_403_user_unauthorized(self): - all_scopes = list(auth.Scope._member_names_) + all_scopes = list(auth.Scope.__members__.keys()) all_scopes.remove(self.scope.name) for scope_str in all_scopes: scope = auth.Scope[scope_str]