From 383c27c9e2a4195eabd3a4c41a25291c09e1134a Mon Sep 17 00:00:00 2001 From: Alexander Goscinski Date: Tue, 21 May 2024 12:21:43 +0200 Subject: [PATCH 1/8] Devops: Updating pydantic version in pre-commit config to 1.10 Updating pydantic version in pre-commit config to 1.10 to fix import error and to be consistent with the version in the pyproject.toml. See issue https://github.com/pydantic/pydantic/issues/3528 --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index fefe4ea..5e716f1 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -24,7 +24,7 @@ repos: hooks: - id: mypy additional_dependencies: - - pydantic~=1.8.2 + - pydantic~=1.10.0 exclude: > (?x)^( docs/.*| @@ -41,7 +41,7 @@ repos: - 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 From 5be10b7f364154c1021ccb1ec361cdb6e8ececb8 Mon Sep 17 00:00:00 2001 From: Alexander Goscinski Date: Tue, 21 May 2024 12:37:43 +0200 Subject: [PATCH 2/8] Devops: Updating mirrors-mypy for pre-commit to 1.10.0 Current version of mirrors-mypy for pre-commit is outdated causing a build of typed_ast during the run that fails so it has been updated to the most recent version 1.10.0. See issue https://github.com/pre-commit/pre-commit/issues/2850 The mypy errors that come with the update have been fixed. --- .pre-commit-config.yaml | 4 +++- aiida_restapi/models.py | 4 +--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5e716f1..c4df4fe 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.10.0 + - types-python-dateutil + - types-docutils exclude: > (?x)^( docs/.*| 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): From 881c5925774cf8ed78d56627490a7e65ecc59338 Mon Sep 17 00:00:00 2001 From: Alexander Goscinski Date: Tue, 21 May 2024 12:57:55 +0200 Subject: [PATCH 3/8] Devops: Update pylint to version 3.2.2 in pre-commmit config Update pylint to newest version 3.2.2 to fix `ImportError: cannot import name 'formatargspec' from 'inspect'`. Linter errors that come with the update have been fixed. --- .pre-commit-config.yaml | 2 +- aiida_restapi/routers/computers.py | 4 ++-- aiida_restapi/routers/daemon.py | 8 ++++---- aiida_restapi/routers/groups.py | 4 ++-- aiida_restapi/routers/nodes.py | 8 ++++---- aiida_restapi/routers/process.py | 6 +++--- aiida_restapi/routers/users.py | 4 ++-- examples/daemon_management/script.py | 11 +++++++---- examples/process_management/script.py | 5 ++--- examples/submit_quantumespresso_pw/script.py | 5 ++--- tests/test_computers.py | 2 +- tests/test_groups.py | 2 +- tests/test_nodes.py | 4 ++-- tests/test_processes.py | 2 +- tests/test_users.py | 2 +- 15 files changed, 35 insertions(+), 34 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c4df4fe..cd32648 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -36,7 +36,7 @@ repos: )$ - repo: https://github.com/PyCQA/pylint - rev: v2.8.3 + rev: v3.2.2 hooks: - id: pylint additional_dependencies: 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/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 From 9fa2d65ec574f922a4bb5318a0f438d583e7e2c4 Mon Sep 17 00:00:00 2001 From: Alexander Goscinski Date: Tue, 21 May 2024 13:01:31 +0200 Subject: [PATCH 4/8] Devops: adding aiida-restapi[auth] to testing dependencies Tests require aiida-restapi[auth] to run, this has been added as dependency to the testing extra dependency. --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index cf60bfa..c1f0638 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', From 034d942b6f22f12db94c6e60b0ccd59e29e9c405 Mon Sep 17 00:00:00 2001 From: Alexander Goscinski Date: Tue, 21 May 2024 13:13:43 +0200 Subject: [PATCH 5/8] Devops: Updating tox file with description and py311 Added py311 tox environment, since it is supported. Added description to each environment. By default tox runs now py38 and py311 to cover the supported range. --- pyproject.toml | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index c1f0638..8d1e264 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -131,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 @@ -150,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 = @@ -162,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 From 607ecac02eda3b981821650912cee65f8df7001e Mon Sep 17 00:00:00 2001 From: Alexander Goscinski Date: Tue, 21 May 2024 10:41:07 +0200 Subject: [PATCH 6/8] Docs: Dev instructions in README.md structured Splitted instructions into cloning, pre-commit and running tests. Added instructions how to run tests with tox. --- README.md | 33 ++++++++++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) 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. From a4d1b8f8e12bf617205076bac7ab846dedceb4af Mon Sep 17 00:00:00 2001 From: Alexander Goscinski Date: Tue, 21 May 2024 13:43:06 +0200 Subject: [PATCH 7/8] Devops: Update Codecov GitHub action to version 4 Fixes the error `Rate limit reached. Please upload with the Codecov repository upload token to resolve issue` from Codecov --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From cffd8a2966dd7bf3b663a4a525a78bf2afb46411 Mon Sep 17 00:00:00 2001 From: Alexander Goscinski Date: Wed, 22 May 2024 12:35:26 +0200 Subject: [PATCH 8/8] Devops: Add codecov token to CI --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 026bafe..53e2ffe 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -72,4 +72,5 @@ jobs: name: pytests flags: pytests file: ./coverage.xml + token: ${{ secrets.CODECOV_TOKEN }} fail_ci_if_error: true