Skip to content

Commit 242f28a

Browse files
authored
Upgrade ruff rules and more (#183)
Add ruff pre-commit hook. Add pyupgrade pre-commit hook. Add blacken-docs pre-commit hook. Update ruff and mypy rules according to scienfic-python.org. Specifically according to the rules here: https://learn.scientific-python.org/development/guides/style/ Make end2end_test.py executable.
1 parent cc1cf8d commit 242f28a

22 files changed

Lines changed: 330 additions & 240 deletions

.github/utils/end2end_test.py

100644100755
Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
#!/usr/bin/env python3
22
"""End-to-end testing in CI for OTEAPI OPTIMADE (using OTELib)."""
3+
from __future__ import annotations
4+
35
import importlib
46
import json
57
import os
@@ -28,13 +30,15 @@ def _check_service_availability(service_url: str) -> None:
2830
"""
2931
import requests
3032

33+
error_message = f"Cannot connect to {service_url} !"
34+
3135
try:
3236
response = requests.get(f"{service_url}/docs", allow_redirects=True, timeout=30)
3337
except (requests.ConnectionError, requests.ConnectTimeout) as exc_:
34-
raise RuntimeError(f"Cannot connect to {service_url} !") from exc_
38+
raise RuntimeError(error_message) from exc_
3539

3640
if not response.ok:
37-
raise RuntimeError(f"Cannot connect to {service_url} !")
41+
raise RuntimeError(error_message)
3842

3943

4044
def main(oteapi_url: str) -> None:
@@ -69,12 +73,12 @@ def main(oteapi_url: str) -> None:
6973

7074
session = source.get()
7175

76+
error_message = "Could not parse returned session as an OPTIMADEResourceStrategy."
77+
7278
try:
7379
session = OPTIMADEResourceSession(**json.loads(session))
7480
except ValidationError as exc_:
75-
raise RuntimeError(
76-
"Could not parse returned session as an OPTIMADEResourceStrategy."
77-
) from exc_
81+
raise RuntimeError(error_message) from exc_
7882

7983
assert session.optimade_resource_model == f"{Structure.__module__}:Structure"
8084
assert len(session.optimade_resources) == 2
@@ -98,9 +102,7 @@ def main(oteapi_url: str) -> None:
98102
# Should be an OPTIMADEResourceSession because `source` is last in the pipeline
99103
session = OPTIMADEResourceSession(**json.loads(session))
100104
except ValidationError as exc_:
101-
raise RuntimeError(
102-
"Could not parse returned session as an OPTIMADEResourceStrategy."
103-
) from exc_
105+
raise RuntimeError(error_message) from exc_
104106

105107
assert session.optimade_resource_model == f"{Structure.__module__}:Structure"
106108
assert len(session.optimade_resources) == 4
@@ -130,10 +132,10 @@ def main(oteapi_url: str) -> None:
130132
# Configuration
131133
PORT = os.getenv("OTEAPI_PORT", "8080")
132134
OTEAPI_SERVICE_URL = f"http://localhost:{PORT}"
133-
OTEAPI_PREFIX = os.getenv("OTEAPI_prefix", "/api/v1")
135+
OTEAPI_PREFIX = os.getenv("OTEAPI_prefix", "/api/v1") # noqa: SIM112
134136
if "OTEAPI_prefix" not in os.environ:
135137
# Set environment variables
136-
os.environ["OTEAPI_prefix"] = OTEAPI_PREFIX
138+
os.environ["OTEAPI_prefix"] = OTEAPI_PREFIX # noqa: SIM112
137139

138140
try:
139141
_check_service_availability(service_url=OTEAPI_SERVICE_URL)

.github/workflows/ci_tests.yml

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,6 @@ jobs:
3333
package_dirs: oteapi_optimade
3434
full_docs_dirs: "models"
3535

36-
ruff:
37-
runs-on: ubuntu-latest
38-
steps:
39-
- name: Checkout repository
40-
uses: actions/checkout@v4
41-
42-
- uses: chartboost/ruff-action@v1
43-
4436
pytest:
4537
name: pytest (${{ matrix.os[1] }}-py${{ matrix.python-version }})
4638
runs-on: ${{ matrix.os[0] }}
@@ -74,11 +66,14 @@ jobs:
7466
run: pytest -vvv --cov-report=xml
7567

7668
- name: Upload coverage to Codecov
77-
if: matrix.python-version == '3.9' && github.repository == 'SINTEF/oteapi-optimade'
69+
if: github.repository == 'SINTEF/oteapi-optimade'
7870
uses: codecov/codecov-action@v3
7971
with:
80-
files: coverage.xml
81-
flags: ${{ matrix.os[1] }}
72+
env_vars: OS,PYTHON
73+
flags: local
74+
env:
75+
OS: ${{ matrix.os[0] }}
76+
PYTHON: ${{ matrix.python-version }}
8277

8378
pytest-real-backend:
8479
runs-on: ubuntu-latest

.pre-commit-config.yaml

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,45 @@
1-
# To install the git pre-commit hook run:
2-
# pre-commit install
3-
# To update the pre-commit hooks run:
4-
# pre-commit autoupdate
1+
default_language_version:
2+
python: python3.9
3+
54
repos:
65
- repo: https://github.com/pre-commit/pre-commit-hooks
76
rev: v4.5.0
87
hooks:
9-
- id: end-of-file-fixer
10-
- id: debug-statements
11-
- id: check-yaml
12-
name: Check YAML
138
- id: check-toml
149
name: Check TOML
10+
- id: check-yaml
11+
name: Check YAML
12+
- id: debug-statements
13+
- id: end-of-file-fixer
14+
- id: mixed-line-ending
15+
- id: name-tests-test
16+
args: ["--pytest-test-first"]
1517
- id: trailing-whitespace
1618
args: [--markdown-linebreak-ext=md]
1719

18-
- repo: https://github.com/astral-sh/ruff-pre-commit
19-
rev: v0.1.6
20+
- repo: https://github.com/asottile/pyupgrade
21+
rev: v3.15.0
2022
hooks:
21-
- id: ruff
22-
args: ["--fix", "--exit-non-zero-on-fix"]
23+
- id: pyupgrade
24+
args: [--py39-plus, --keep-runtime-typing]
2325

2426
- repo: https://github.com/ambv/black
2527
rev: 23.11.0
2628
hooks:
2729
- id: black
2830

31+
- repo: https://github.com/adamchainz/blacken-docs
32+
rev: 1.16.0
33+
hooks:
34+
- id: blacken-docs
35+
additional_dependencies: [black]
36+
37+
- repo: https://github.com/astral-sh/ruff-pre-commit
38+
rev: v0.1.5
39+
hooks:
40+
- id: ruff
41+
args: ["--fix", "--exit-non-zero-on-fix", "--show-fixes"]
42+
2943
- repo: https://github.com/PyCQA/bandit
3044
rev: 1.7.5
3145
hooks:

oteapi_optimade/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
Authored by Casper Welzel Andersen, SINTEF, 2022
66
Created from cookiecutter-oteapi-plugin, SINTEF, 2022
77
"""
8+
from __future__ import annotations
89

910
__version__ = "0.4.2"
1011
__author__ = "Casper Welzel Andersen"

oteapi_optimade/dlite/parse.py

Lines changed: 31 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
"""OTEAPI strategy for parsing OPTIMADE structure resources to DLite instances."""
2+
from __future__ import annotations
3+
24
import logging
35
from pathlib import Path
46
from typing import TYPE_CHECKING
@@ -17,7 +19,7 @@
1719
from oteapi_optimade.strategies.parse import OPTIMADEParseStrategy
1820

1921
if TYPE_CHECKING: # pragma: no cover
20-
from typing import Any, Dict, Optional, Union
22+
from typing import Any
2123

2224

2325
LOGGER = logging.getLogger("oteapi_optimade.dlite")
@@ -40,9 +42,7 @@ class OPTIMADEDLiteParseStrategy:
4042

4143
parse_config: OPTIMADEDLiteParseConfig
4244

43-
def initialize(
44-
self, session: "Optional[Dict[str, Any]]" = None
45-
) -> DLiteSessionUpdate:
45+
def initialize(self, session: dict[str, Any] | None = None) -> DLiteSessionUpdate:
4646
"""Initialize strategy.
4747
4848
This method will be called through the `/initialize` endpoint of the OTE-API
@@ -59,7 +59,7 @@ def initialize(
5959
return DLiteSessionUpdate(collection_id=get_collection(session).uuid)
6060

6161
def get(
62-
self, session: "Optional[Union[SessionUpdate, Dict[str, Any]]]" = None
62+
self, session: SessionUpdate | dict[str, Any] | None = None
6363
) -> OPTIMADEParseSession:
6464
"""Request and parse an OPTIMADE response using OPT.
6565
@@ -110,14 +110,19 @@ def get(
110110
f"yaml://{entities_path}/OPTIMADEStructureSpecies.yaml"
111111
)
112112

113+
error_message_supporting_only_structures = (
114+
"The DLite OPTIMADE Parser currently only supports structures entities."
115+
)
116+
113117
if self.parse_config.configuration.return_object:
114118
# The response is given as a "proper" pydantic data model instance
115119

116120
if "optimade_response_object" not in session:
117-
raise ValueError(
121+
error_message = (
118122
"'optimade_response_object' was expected to be present in the "
119123
"session."
120124
)
125+
raise ValueError(error_message)
121126

122127
# Currently, only "structures" entries are supported and handled
123128
if isinstance(session.optimade_response_object, StructureResponseMany):
@@ -152,26 +157,23 @@ def get(
152157
"Could not determine what to do with `data`. Type %s.",
153158
type(session.optimade_response_object.data),
154159
)
155-
raise OPTIMADEParseError(
156-
"Could not parse `data` entry in response."
157-
)
160+
error_message = "Could not parse `data` entry in response."
161+
raise OPTIMADEParseError(error_message)
158162
else:
159163
LOGGER.debug(
160164
"Got currently unsupported response type %s. Only structures are "
161165
"supported.",
162166
session.optimade_response_object.__class__.__name__,
163167
)
164-
raise OPTIMADEParseError(
165-
"The DLite OPTIMADE Parser currently only supports structures "
166-
"entities."
167-
)
168+
raise OPTIMADEParseError(error_message_supporting_only_structures)
168169
else:
169170
# The response is given as pure Python dictionary
170171

171172
if "optimade_response" not in session:
172-
raise ValueError(
173+
error_message = (
173174
"'optimade_response' was expected to be present in the session."
174175
)
176+
raise ValueError(error_message)
175177

176178
if not session.optimade_response or "data" not in session.optimade_response:
177179
LOGGER.debug("Not a successful response - no 'data' entry found.")
@@ -187,30 +189,25 @@ def get(
187189
"Could not parse list of 'data' entries as structures."
188190
)
189191
raise OPTIMADEParseError(
190-
"The DLite OPTIMADE Parser currently only supports structures "
191-
"entities."
192+
error_message_supporting_only_structures
192193
) from exc
193194
elif session.optimade_response is not None:
194195
try:
195196
structures = [Structure(session.optimade_response["data"])]
196197
except ValidationError as exc:
197198
LOGGER.debug("Could not parse single 'data' entry as a structure.")
198199
raise OPTIMADEParseError(
199-
"The DLite OPTIMADE Parser currently only supports structures "
200-
"entities."
200+
error_message_supporting_only_structures
201201
) from exc
202202
else:
203-
LOGGER.debug("Could not parse 'data' entries as structures.")
204-
raise OPTIMADEParseError(
205-
"The DLite OPTIMADE Parser currently only supports structures "
206-
"entities."
207-
)
203+
LOGGER.debug("Could not parse 'data' entries as structures.") # type: ignore[unreachable]
204+
raise OPTIMADEParseError(error_message_supporting_only_structures)
208205

209206
dlite_collection = get_collection(session)
210207

211208
# DLite-fy OPTIMADE structures
212209
for structure in structures:
213-
new_structure_attributes: dict[str, "Any"] = {}
210+
new_structure_attributes: dict[str, Any] = {}
214211

215212
# Most inner layer: assemblies & species
216213
if structure.attributes.assemblies:
@@ -221,19 +218,21 @@ def get(
221218

222219
for assembly in structure.attributes.assemblies:
223220
# Ensure we're dealing with a normal Python dict
224-
assembly = (
221+
assembly_dict = (
225222
assembly.dict(exclude_none=True)
226223
if isinstance(assembly, BaseModel)
227224
else assembly
228225
)
229226

230227
dimensions = {
231-
"ngroups": len(assembly.get("group_probabilities", []) or []),
232-
"nsites": len(assembly.get("sites_in_groups", []) or []),
228+
"ngroups": len(
229+
assembly_dict.get("group_probabilities", []) or []
230+
),
231+
"nsites": len(assembly_dict.get("sites_in_groups", []) or []),
233232
}
234233
new_structure_attributes["assemblies"].append(
235234
OPTIMADEStructureAssembly(
236-
dimensions=dimensions, properties=assembly
235+
dimensions=dimensions, properties=assembly_dict
237236
)
238237
)
239238

@@ -245,24 +244,24 @@ def get(
245244

246245
for species_individual in structure.attributes.species:
247246
# Ensure we're dealing with a normal Python dict
248-
species_individual = (
247+
species_individual_dict = (
249248
species_individual.dict(exclude_none=True)
250249
if isinstance(species_individual, BaseModel)
251250
else species_individual
252251
)
253252

254253
dimensions = {
255254
"nelements": len(
256-
species_individual.get("chemical_symbols", []) or []
255+
species_individual_dict.get("chemical_symbols", []) or []
257256
),
258257
"nattached_elements": len(
259-
species_individual.get("attached", []) or []
258+
species_individual_dict.get("attached", []) or []
260259
),
261260
}
262261
new_structure_attributes["species"].append(
263262
OPTIMADEStructureSpecies(
264263
dimensions=dimensions,
265-
properties=species_individual,
264+
properties=species_individual_dict,
266265
)
267266
)
268267

oteapi_optimade/exceptions.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
"""OTE-API OPTIMADE-specific Python exceptions."""
2+
from __future__ import annotations
23

34

45
class BaseOteapiOptimadeException(Exception):

oteapi_optimade/models/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
"""`oteapi_optimade.models` module - pydantic data models."""
2+
from __future__ import annotations
3+
24
from .strategies import (
35
OPTIMADEDLiteParseConfig,
46
OPTIMADEFilterConfig,

oteapi_optimade/models/config.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
"""General OPTIMADE configuration models."""
2+
from __future__ import annotations
3+
24
from typing import Literal, Optional
35

46
from oteapi.models import AttrDict, DataCacheConfig
@@ -13,7 +15,7 @@
1315
"""Set the `expireTime` and `tag` to default values for the data cache."""
1416

1517

16-
class OPTIMADEConfig(AttrDict):
18+
class OPTIMADEConfig(AttrDict): # type: ignore[misc]
1719
"""OPTIMADE configuration."""
1820

1921
version: str = Field(

0 commit comments

Comments
 (0)