Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use pydantic > 1 #192

Merged
merged 28 commits into from
Jan 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
46282da
Use `pydantic` > 1
dotsdl Oct 12, 2023
83c97f5
Updates to deprecated pydantic decorators
dotsdl Oct 19, 2023
9b7670c
Think this fixes model_validator usage
dotsdl Oct 19, 2023
f60979c
More pydantic 2 updates
dotsdl Oct 20, 2023
6eb5af9
Black!
dotsdl Oct 20, 2023
ec784b8
Merge branch 'main' into pydantic-2
dotsdl Oct 20, 2023
c725fb3
More pydantic updates...
dotsdl Oct 20, 2023
a7801b8
Think I finally have tests working again
dotsdl Oct 20, 2023
7053c7c
Merge branch 'main' into pydantic-2
dotsdl Oct 20, 2023
435cc44
Fix broken docs
dotsdl Oct 20, 2023
110c45e
Merge branch 'main' into pydantic-2
dotsdl Nov 9, 2023
8bac219
More pydantic fixes; added docstring to authenticate function
dotsdl Nov 9, 2023
28e7ce7
Merge branch 'main' into pydantic-2
dotsdl Nov 9, 2023
e3d11e6
Force newer `openff-models`
dotsdl Nov 9, 2023
23f8b75
Update CI to use latest best practices for micromamba
dotsdl Nov 9, 2023
b3f329d
Merge branch 'main' into pydantic-2
dotsdl Nov 17, 2023
fb32353
Fix CI
dotsdl Nov 17, 2023
302930e
Merge branch 'main' into pydantic-2
dotsdl Nov 20, 2023
0a4ccd8
Add pydantic-settings to test env
dotsdl Nov 21, 2023
45775ac
Merge branch 'main' into pydantic-2
dotsdl Mar 28, 2024
7ff5924
Merge remote-tracking branch 'upstream/main' into HEAD
mattwthompson Jun 11, 2024
c2d6cca
Merge branch 'main' into pydantic-2
dotsdl Jul 3, 2024
2d4a16c
Merge branch 'main' into pydantic-2
dotsdl Jan 22, 2025
2776119
Pydantic 2 fixes
dotsdl Jan 22, 2025
7ff8f66
Add scope hierarchy check to ScopedKeys model; force use of pydantic …
dotsdl Jan 23, 2025
305441a
Black!
dotsdl Jan 23, 2025
4c1ad3b
Merge branch 'main' into pydantic-2
dotsdl Jan 24, 2025
586320e
Edits from @ianmkenney review
dotsdl Jan 24, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/ci-integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ jobs:
steps:
- uses: actions/checkout@v4

- uses: mamba-org/setup-micromamba@v1
- name: Install environment
uses: mamba-org/setup-micromamba@v1
with:
environment-file: devtools/conda-envs/test.yml
create-args: >-
Expand Down
2 changes: 1 addition & 1 deletion alchemiscale/compute/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ def register_computeservice(
):
now = datetime.utcnow()
csreg = ComputeServiceRegistration(
identifier=compute_service_id, registered=now, heartbeat=now
identifier=ComputeServiceID(compute_service_id), registered=now, heartbeat=now
)

compute_service_id_ = n4js.register_computeservice(csreg)
Expand Down
42 changes: 29 additions & 13 deletions alchemiscale/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@

"""

from typing import Optional, Union
from pydantic import BaseModel, Field, validator, root_validator
from typing import Optional, Union, Any
from pydantic import BaseModel, field_validator, model_validator, ConfigDict
from gufe.tokenization import GufeKey
from re import fullmatch
import unicodedata
Expand All @@ -17,6 +17,10 @@
campaign: Optional[str] = None
project: Optional[str] = None

model_config = ConfigDict(
frozen=True,
)

def __init__(self, org=None, campaign=None, project=None):
# we add this to allow for arg-based creation, not just keyword-based
super().__init__(org=org, campaign=campaign, project=project)
Expand All @@ -36,9 +40,6 @@

return str(self) == str(other)

class Config:
frozen = True

@staticmethod
def _validate_component(v, component):
"""
Expand All @@ -63,20 +64,24 @@

return v

@validator("org")
@field_validator("org")
@classmethod
def valid_org(cls, v):
return cls._validate_component(v, "org")

@validator("campaign")
@field_validator("campaign")
@classmethod
def valid_campaign(cls, v):
return cls._validate_component(v, "campaign")

@validator("project")
@field_validator("project")
@classmethod
def valid_project(cls, v):
return cls._validate_component(v, "project")

@root_validator
def check_scope_hierarchy(cls, values):
@model_validator(mode="before")
@classmethod
def check_scope_hierarchy(cls, values: Any) -> Any:
if not _hierarchy_valid(values):
raise InvalidScopeError(
f"Invalid scope hierarchy: {values}, cannot specify wildcard ('*')"
Expand Down Expand Up @@ -132,10 +137,10 @@
campaign: str
project: str

class Config:
frozen = True
model_config = ConfigDict(frozen=True, arbitrary_types_allowed=True)

@validator("gufe_key")
@field_validator("gufe_key", mode="before")
@classmethod
def gufe_key_validator(cls, v):
v = str(v)

Expand All @@ -157,6 +162,17 @@
# Cast to GufeKey
return GufeKey(v_normalized)

@model_validator(mode="before")
@classmethod
def check_scope_hierarchy(cls, values: Any) -> Any:
if not _hierarchy_valid(values):
raise InvalidScopeError(

Check warning on line 169 in alchemiscale/models.py

View check run for this annotation

Codecov / codecov/patch

alchemiscale/models.py#L169

Added line #L169 was not covered by tests
f"Invalid scope hierarchy: {values}, cannot specify wildcard ('*')"
" in a scope component if a less specific scope component is not"
" given, unless all components are wildcards (*-*-*)."
)
return values

def __repr__(self): # pragma: no cover
return f"<ScopedKey('{str(self)}')>"

Expand Down
25 changes: 22 additions & 3 deletions alchemiscale/security/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,31 @@
return secrets.token_hex(32)


def authenticate(db, cls, identifier: str, key: str) -> CredentialedEntity:
def authenticate(db, cls, identifier: str, key: str) -> Optional[CredentialedEntity]:
"""Authenticate the given identity+key against the db instance.

Parameters
----------
db
State store instance featuring a `get_credentialed_entity` method.
cls
The `CredentialedEntity` subclass the identity corresponds to.
identity
String identifier for the the identity.
key
Secret key string for the identity.

Returns
-------
If successfully authenticated, returns the `CredentialedEntity` subclass instance.
If not, returns `None`.

"""
entity: CredentialedEntity = db.get_credentialed_entity(identifier, cls)
if entity is None:
return False
return None

Check warning on line 97 in alchemiscale/security/auth.py

View check run for this annotation

Codecov / codecov/patch

alchemiscale/security/auth.py#L97

Added line #L97 was not covered by tests
if not pwd_context.verify(key, entity.hashed_key):
return False
return None

Check warning on line 99 in alchemiscale/security/auth.py

View check run for this annotation

Codecov / codecov/patch

alchemiscale/security/auth.py#L99

Added line #L99 was not covered by tests
return entity


Expand Down
27 changes: 15 additions & 12 deletions alchemiscale/security/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from datetime import datetime, timedelta
from typing import List, Union, Optional

from pydantic import BaseModel, validator
from pydantic import BaseModel, field_validator

from ..models import Scope

Expand All @@ -32,20 +32,23 @@
disabled: bool = False
scopes: List[str] = []

@validator("scopes", pre=True, each_item=True)
def cast_scopes_to_str(cls, scope):
@field_validator("scopes", mode="before")
@classmethod
def cast_scopes_to_str(cls, scopes):
"""Ensure that each scope object is correctly cast to its str representation"""
if isinstance(scope, Scope):
scope = str(scope)
elif isinstance(scope, str):
try:
Scope.from_str(scope)
except:
scopes_ = []
for scope in scopes:
if isinstance(scope, Scope):
scopes_.append(str(scope))
elif isinstance(scope, str):
try:
scopes_.append(str(Scope.from_str(scope)))
except:
raise ValueError(f"Invalid scope `{scope}` set for `{cls}`")

Check warning on line 47 in alchemiscale/security/models.py

View check run for this annotation

Codecov / codecov/patch

alchemiscale/security/models.py#L43-L47

Added lines #L43 - L47 were not covered by tests
else:
raise ValueError(f"Invalid scope `{scope}` set for `{cls}`")
else:
raise ValueError(f"Invalid scope `{scope}` set for `{cls}`")

return scope
return scopes_


class UserIdentity(ScopedIdentity):
Expand Down
7 changes: 4 additions & 3 deletions alchemiscale/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@
from functools import lru_cache
from typing import Optional

from pydantic import BaseSettings
from pydantic_settings import BaseSettings, SettingsConfigDict


class FrozenSettings(BaseSettings):
class Config:
frozen = True
model_config = SettingsConfigDict(
frozen=True,
)


class Neo4jStoreSettings(FrozenSettings):
Expand Down
6 changes: 5 additions & 1 deletion alchemiscale/storage/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
import hashlib


from pydantic import BaseModel
from pydantic import BaseModel, ConfigDict
from gufe.tokenization import GufeTokenizable, GufeKey

from ..models import ScopedKey, Scope
Expand All @@ -29,6 +29,8 @@ class ComputeServiceRegistration(BaseModel):
registered: datetime
heartbeat: datetime

model_config = ConfigDict(arbitrary_types_allowed=True)

def __repr__(self): # pragma: no cover
return f"<ComputeServiceRegistration('{str(self)}')>"

Expand Down Expand Up @@ -59,6 +61,8 @@ class TaskProvenance(BaseModel):
datetime_start: datetime
datetime_end: datetime

model_config = ConfigDict(arbitrary_types_allowed=True)

# this should include versions of various libraries


Expand Down
3 changes: 2 additions & 1 deletion devtools/conda-envs/alchemiscale-client.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ dependencies:
- requests
- click
- httpx
- pydantic<2.0
- pydantic >2
- pydantic-settings
- async-lru

## user client
Expand Down
3 changes: 2 additions & 1 deletion devtools/conda-envs/alchemiscale-compute.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ dependencies:
- requests
- click
- httpx
- pydantic<2.0
- pydantic >2
- pydantic-settings
- async-lru

# openmm protocols
Expand Down
3 changes: 2 additions & 1 deletion devtools/conda-envs/alchemiscale-server.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ dependencies:

- requests
- click
- pydantic<2.0
- pydantic >2
- pydantic-settings
- async-lru

## state store
Expand Down
3 changes: 2 additions & 1 deletion devtools/conda-envs/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ dependencies:
# alchemiscale dependencies
- gufe>=1.1.0
- openfe>=1.2.0
- pydantic<2.0
- pydantic >2
- pydantic-settings
- async-lru

## state store
Expand Down
1 change: 1 addition & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
"numpy",
"py2neo",
"pydantic",
"pydantic_settings",
"starlette",
"yaml",
]
Expand Down
Loading