diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 35f44fd..026bafe 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -67,7 +67,7 @@ jobs: - name: Upload to Codecov if: matrix.python-version == 3.8 - uses: codecov/codecov-action@v1 + uses: codecov/codecov-action@v4 with: name: pytests flags: pytests diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index fefe4ea..cd32648 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -20,11 +20,13 @@ repos: - id: black - repo: https://github.com/pre-commit/mirrors-mypy - rev: v0.812 + rev: v1.10.0 hooks: - id: mypy additional_dependencies: - - pydantic~=1.8.2 + - pydantic~=1.10.0 + - types-python-dateutil + - types-docutils exclude: > (?x)^( docs/.*| @@ -34,14 +36,14 @@ repos: )$ - repo: https://github.com/PyCQA/pylint - rev: v2.8.3 + rev: v3.2.2 hooks: - id: pylint additional_dependencies: - aiida-core~=2.0 - fastapi~=0.65.1 - uvicorn[standard]~=0.19.0 - - pydantic~=1.8.2 + - pydantic~=1.10.0 - graphene<3 - lark - python-dateutil~=2.0 diff --git a/README.md b/README.md index 6289a2a..b92ce21 100644 --- a/README.md +++ b/README.md @@ -43,9 +43,36 @@ See the [examples](https://github.com/aiidateam/aiida-restapi/tree/master/exampl ```shell git clone https://github.com/aiidateam/aiida-restapi . cd aiida-restapi -pip install -e .[pre-commit,testing] # install extra dependencies -pre-commit install # install pre-commit hooks -pytest -v # discover and run all tests +``` + +### Setting up pre-commit + +We use pre-commit to take care for the formatting, type checking and linting. +```shell +pip install -e .[pre-commit] # install extra dependencies +pre-commit run # running pre-commit on changes +pre-commit run --all-files # running pre-commit on every file +pre-commit run pylint --all-files # run only the linter on every file +``` +One can also set up pre-commit to be run on every commit +```shell +pre-commit install +# pre-commit uninstall # to disable it again +``` + +### Running tests + +With tox the tests can be run +```shell +pip install tox +tox -e py311 # run all tests for Python 3.11 +tox -av # see all supported environments +``` +tox will creat a custom environment to run the tests in. If you want to run the +tests inside your current environment +```shell +pip install -e .[testing] # install extra dependencies +pytest -v ``` See the [developer guide](http://aiida-restapi.readthedocs.io/en/latest/developer_guide/index.html) for more information. diff --git a/aiida_restapi/models.py b/aiida_restapi/models.py index 73f04da..7dccf0d 100644 --- a/aiida_restapi/models.py +++ b/aiida_restapi/models.py @@ -94,9 +94,7 @@ def get_entities( order_by ), f"order_by not subset of projectable properties: {project!r}" query.order_by({"fields": order_by}) - return [ - cls(**result["fields"]) for result in query.dict() # type: ignore[call-arg] - ] + return [cls(**result["fields"]) for result in query.dict()] class Comment(AiidaModel): diff --git a/aiida_restapi/routers/computers.py b/aiida_restapi/routers/computers.py index 2e6d483..ba3f147 100644 --- a/aiida_restapi/routers/computers.py +++ b/aiida_restapi/routers/computers.py @@ -45,9 +45,9 @@ async def read_computer(comp_id: int) -> Optional[Computer]: @with_dbenv() async def create_computer( computer: Computer, - current_user: User = Depends( + current_user: User = Depends( # pylint: disable=unused-argument get_current_active_user - ), # pylint: disable=unused-argument + ), ) -> Computer: """Create new AiiDA computer.""" orm_computer = orm.Computer(**computer.dict(exclude_unset=True)).store() diff --git a/aiida_restapi/routers/daemon.py b/aiida_restapi/routers/daemon.py index 5067667..f10cba6 100644 --- a/aiida_restapi/routers/daemon.py +++ b/aiida_restapi/routers/daemon.py @@ -41,9 +41,9 @@ async def get_daemon_status() -> DaemonStatusModel: @router.post("/daemon/start", response_model=DaemonStatusModel) @with_dbenv() async def get_daemon_start( - current_user: User = Depends( + current_user: User = Depends( # pylint: disable=unused-argument get_current_active_user - ), # pylint: disable=unused-argument + ), ) -> DaemonStatusModel: """Start the daemon.""" client = get_daemon_client() @@ -64,9 +64,9 @@ async def get_daemon_start( @router.post("/daemon/stop", response_model=DaemonStatusModel) @with_dbenv() async def get_daemon_stop( - current_user: User = Depends( + current_user: User = Depends( # pylint: disable=unused-argument get_current_active_user - ), # pylint: disable=unused-argument + ), ) -> DaemonStatusModel: """Stop the daemon.""" client = get_daemon_client() diff --git a/aiida_restapi/routers/groups.py b/aiida_restapi/routers/groups.py index 3806b9b..59a2f5b 100644 --- a/aiida_restapi/routers/groups.py +++ b/aiida_restapi/routers/groups.py @@ -44,9 +44,9 @@ async def read_group(group_id: int) -> Optional[Group]: @with_dbenv() async def create_group( group: Group_Post, - current_user: User = Depends( + current_user: User = Depends( # pylint: disable=unused-argument get_current_active_user - ), # pylint: disable=unused-argument + ), ) -> Group: """Create new AiiDA group.""" orm_group = orm.Group(**group.dict(exclude_unset=True)).store() diff --git a/aiida_restapi/routers/nodes.py b/aiida_restapi/routers/nodes.py index f5e3bcb..b6c315b 100644 --- a/aiida_restapi/routers/nodes.py +++ b/aiida_restapi/routers/nodes.py @@ -42,9 +42,9 @@ async def read_node(nodes_id: int) -> Optional[models.Node]: @with_dbenv() async def create_node( node: models.Node_Post, - current_user: models.User = Depends( + current_user: models.User = Depends( # pylint: disable=unused-argument get_current_active_user - ), # pylint: disable=unused-argument + ), ) -> models.Node: """Create new AiiDA node.""" node_dict = node.dict(exclude_unset=True) @@ -68,9 +68,9 @@ async def create_node( async def create_upload_file( upload_file: bytes = File(...), params: models.Node_Post = Depends(models.Node_Post.as_form), # type: ignore # pylint: disable=maybe-no-member - current_user: models.User = Depends( + current_user: models.User = Depends( # pylint: disable=unused-argument get_current_active_user - ), # pylint: disable=unused-argument + ), ) -> models.Node: """Endpoint for uploading file data""" node_dict = params.dict(exclude_unset=True, exclude_none=True) diff --git a/aiida_restapi/routers/process.py b/aiida_restapi/routers/process.py index 0a82d9e..ab7571c 100644 --- a/aiida_restapi/routers/process.py +++ b/aiida_restapi/routers/process.py @@ -79,9 +79,9 @@ async def read_process(proc_id: int) -> Optional[Process]: @with_dbenv() async def post_process( process: Process_Post, - current_user: User = Depends( + current_user: User = Depends( # pylint: disable=unused-argument get_current_active_user - ), # pylint: disable=unused-argument + ), ) -> Optional[Process]: """Create new process.""" process_dict = process.dict(exclude_unset=True, exclude_none=True) @@ -93,7 +93,7 @@ async def post_process( except ValueError as exc: raise HTTPException( status_code=404, - detail="Entry point '{}' not recognized.".format(entry_point), + detail=f"Entry point '{entry_point}' not recognized.", ) from exc process_node = submit(entry_point_process, **inputs) diff --git a/aiida_restapi/routers/users.py b/aiida_restapi/routers/users.py index 1c0b469..147a754 100644 --- a/aiida_restapi/routers/users.py +++ b/aiida_restapi/routers/users.py @@ -42,9 +42,9 @@ async def read_user(user_id: int) -> Optional[User]: @with_dbenv() async def create_user( user: User, - current_user: User = Depends( + current_user: User = Depends( # pylint: disable=unused-argument get_current_active_user - ), # pylint: disable=unused-argument + ), ) -> User: """Create new AiiDA user.""" orm_user = orm.User(**user.dict(exclude_unset=True)).store() diff --git a/examples/daemon_management/script.py b/examples/daemon_management/script.py index 9e35e7e..ecb9058 100755 --- a/examples/daemon_management/script.py +++ b/examples/daemon_management/script.py @@ -44,8 +44,12 @@ def request( else: headers = {} - response = requests.request( - method, f"{BASE_URL}/{url}", json=json, data=data, headers=headers + response = requests.request( # pylint: disable=missing-timeout + method, + f"{BASE_URL}/{url}", + json=json, + data=data, + headers=headers, ) try: @@ -62,8 +66,7 @@ def request( click.echo(error["message"]) return None - else: - return response.json() + return response.json() def authenticate( diff --git a/examples/process_management/script.py b/examples/process_management/script.py index 172f0e4..1d04672 100755 --- a/examples/process_management/script.py +++ b/examples/process_management/script.py @@ -45,7 +45,7 @@ def request( else: headers = {} - response = requests.request( + response = requests.request( # pylint: disable=missing-timeout method, f"{BASE_URL}/{url}", json=json, data=data, headers=headers ) @@ -63,8 +63,7 @@ def request( click.echo(error["message"]) return None - else: - return response.json() + return response.json() def authenticate( diff --git a/examples/submit_quantumespresso_pw/script.py b/examples/submit_quantumespresso_pw/script.py index ba369d2..cd5c9e7 100755 --- a/examples/submit_quantumespresso_pw/script.py +++ b/examples/submit_quantumespresso_pw/script.py @@ -44,7 +44,7 @@ def request( else: headers = {} - response = requests.request( + response = requests.request( # pylint: disable=missing-timeout method, f"{BASE_URL}/{url}", json=json, data=data, headers=headers ) @@ -62,8 +62,7 @@ def request( click.echo(error["message"]) return None - else: - return response.json() + return response.json() def authenticate( diff --git a/pyproject.toml b/pyproject.toml index cf60bfa..8d1e264 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -55,6 +55,7 @@ pre-commit = [ 'pre-commit~=2.12' ] testing = [ + 'aiida-restapi[auth]', 'pgtest~=1.3.1', 'wheel~=0.31', 'coverage', @@ -130,12 +131,19 @@ warn_untyped_fields = false [tool.tox] legacy_tox_ini = """ [tox] -envlist = py38 +envlist = + py38 + py311 [testenv] usedevelop = true -[testenv:py{38,39,310}] +[testenv:py{38,39,310,311}] +description = + py38: Installs test dependencies and runs tests using python 3.8 + py39: Installs test dependencies and runs tests using python 3.9 + py310: Installs test dependencies and runs tests using python 3.10 + py311: Installs test dependencies and runs tests using python 3.11 extras = auth testing @@ -149,11 +157,15 @@ commands = pytest {posargs} # now you can run `tox -e verdi quicksetup`, then `tox -e serve` [testenv:verdi] +description = + Runs a verdi command within a tox environment that sets the AIIDA_PATH setenv = AIIDA_PATH = {toxinidir}/.tox/.aiida commands = verdi {posargs} [testenv:serve] +description = + Start the web API server within a tox environment that sets the AIIDA_PATH extras = auth setenv = @@ -161,6 +173,9 @@ setenv = commands = uvicorn aiida_restapi:app {posargs:--reload} [testenv:docs-{update,clean}] +description = + docs-clean: Build the documentation (remove any existing build) + docs-update: Build the documentation (modify any existing build) extras = auth docs diff --git a/tests/test_computers.py b/tests/test_computers.py index f93b4e0..7d47d60 100644 --- a/tests/test_computers.py +++ b/tests/test_computers.py @@ -33,7 +33,7 @@ def test_get_single_computers( """Test retrieving a single computer.""" for comp_id in default_computers: - response = client.get("/computers/{}".format(comp_id)) + response = client.get(f"/computers/{comp_id}") assert response.status_code == 200 diff --git a/tests/test_groups.py b/tests/test_groups.py index bb732f0..8268d20 100644 --- a/tests/test_groups.py +++ b/tests/test_groups.py @@ -30,7 +30,7 @@ def test_get_single_group(default_groups, client): # pylint: disable=unused-arg """Test retrieving a single group.""" for group_id in default_groups: - response = client.get("/groups/{}".format(group_id)) + response = client.get(f"/groups/{group_id}") assert response.status_code == 200 diff --git a/tests/test_nodes.py b/tests/test_nodes.py index df8ae9f..94d1999 100644 --- a/tests/test_nodes.py +++ b/tests/test_nodes.py @@ -31,7 +31,7 @@ def test_get_single_nodes(default_nodes, client): # pylint: disable=unused-argu """Test retrieving a single nodes.""" for nodes_id in default_nodes: - response = client.get("/nodes/{}".format(nodes_id)) + response = client.get(f"/nodes/{nodes_id}") assert response.status_code == 200 @@ -299,7 +299,7 @@ def test_create_bool_with_extra( }, ) - check_response = client.get("/nodes/{}".format(response.json()["id"])) + check_response = client.get(f"/nodes/{response.json()['id']}") assert check_response.status_code == 200, response.content assert check_response.json()["extras"]["extra_one"] == "value_1" diff --git a/tests/test_processes.py b/tests/test_processes.py index 66285a3..25b468d 100644 --- a/tests/test_processes.py +++ b/tests/test_processes.py @@ -40,7 +40,7 @@ def test_get_single_processes( ): # pylint: disable=unused-argument """Test retrieving a single processes.""" for proc_id in example_processes: - response = client.get("/processes/{}".format(proc_id)) + response = client.get(f"/processes/{proc_id}") assert response.status_code == 200 diff --git a/tests/test_users.py b/tests/test_users.py index f21cc55..6cd86d8 100644 --- a/tests/test_users.py +++ b/tests/test_users.py @@ -5,7 +5,7 @@ def test_get_single_user(default_users, client): # pylint: disable=unused-argument """Test retrieving a single user.""" for user_id in default_users: - response = client.get("/users/{}".format(user_id)) + response = client.get(f"/users/{user_id}") assert response.status_code == 200