-
Notifications
You must be signed in to change notification settings - Fork 30
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
19 changed files
with
669 additions
and
92 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,67 +1,13 @@ | ||
import os | ||
from typing import ( | ||
Annotated, | ||
Any, | ||
Type, | ||
) | ||
|
||
import pydantic | ||
import pydantic_settings | ||
|
||
from dl_auth_api_lib.utils.pydantic import make_dict_factory | ||
import dl_settings | ||
|
||
|
||
class BaseOAuthClient(pydantic.BaseModel): | ||
class BaseOAuthClient(dl_settings.TypedBaseModel): | ||
type: str = pydantic.Field(alias="auth_type") | ||
conn_type: str | ||
auth_type: str | ||
|
||
@classmethod | ||
def factory(cls, data: Any) -> "BaseOAuthClient": | ||
if isinstance(data, BaseOAuthClient): | ||
return data | ||
|
||
assert isinstance(data, dict), "OAuthClient settings must be a dict" | ||
assert "auth_type" in data, "No auth_type in client" | ||
assert data["auth_type"] in _REGISTRY, f"No such OAuth type: {data['auth_type']}" | ||
|
||
config_class = _REGISTRY[data["auth_type"]] | ||
return config_class.model_validate(data) | ||
|
||
|
||
_REGISTRY: dict[str, type[BaseOAuthClient]] = {} | ||
|
||
|
||
def register_auth_client( | ||
name: str, | ||
auth_type: type[BaseOAuthClient], | ||
) -> None: | ||
_REGISTRY[name] = auth_type | ||
|
||
|
||
class AuthAPISettings(pydantic_settings.BaseSettings): | ||
model_config = pydantic_settings.SettingsConfigDict(env_nested_delimiter="__") | ||
|
||
auth_clients: Annotated[ | ||
dict[str, pydantic.SerializeAsAny[BaseOAuthClient]], | ||
pydantic.BeforeValidator(make_dict_factory(BaseOAuthClient.factory)), | ||
] = pydantic.Field(default=dict()) | ||
|
||
class AuthAPISettings(dl_settings.BaseRootSettings): | ||
auth_clients: dl_settings.TypedDictAnnotation[BaseOAuthClient] = pydantic.Field(default=dict()) | ||
sentry_dsn: str | None = pydantic.Field(default=None) | ||
|
||
@classmethod | ||
def settings_customise_sources( | ||
cls, | ||
settings_cls: Type[pydantic_settings.BaseSettings], | ||
init_settings: pydantic_settings.PydanticBaseSettingsSource, | ||
env_settings: pydantic_settings.PydanticBaseSettingsSource, | ||
dotenv_settings: pydantic_settings.PydanticBaseSettingsSource, | ||
file_secret_settings: pydantic_settings.PydanticBaseSettingsSource, | ||
) -> tuple[pydantic_settings.PydanticBaseSettingsSource, ...]: | ||
return ( | ||
env_settings, | ||
pydantic_settings.YamlConfigSettingsSource( | ||
settings_cls, | ||
yaml_file=os.environ.get("CONFIG_PATH", None), | ||
), | ||
init_settings, | ||
) |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
from .base import ( | ||
BaseRootSettings, | ||
BaseSettings, | ||
TypedAnnotation, | ||
TypedBaseModel, | ||
TypedDictAnnotation, | ||
TypedListAnnotation, | ||
) | ||
|
||
|
||
__all__ = [ | ||
"BaseSettings", | ||
"BaseRootSettings", | ||
"TypedBaseModel", | ||
"TypedAnnotation", | ||
"TypedListAnnotation", | ||
"TypedDictAnnotation", | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
from .settings import ( | ||
BaseRootSettings, | ||
BaseSettings, | ||
) | ||
from .typed import ( | ||
TypedAnnotation, | ||
TypedBaseModel, | ||
TypedDictAnnotation, | ||
TypedListAnnotation, | ||
) | ||
|
||
|
||
__all__ = [ | ||
"BaseSettings", | ||
"BaseRootSettings", | ||
"TypedBaseModel", | ||
"TypedAnnotation", | ||
"TypedListAnnotation", | ||
"TypedDictAnnotation", | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
import os | ||
import typing | ||
|
||
import pydantic_settings | ||
|
||
|
||
class BaseSettings(pydantic_settings.BaseSettings): | ||
... | ||
|
||
|
||
class BaseRootSettings(BaseSettings): | ||
model_config = pydantic_settings.SettingsConfigDict( | ||
env_nested_delimiter="__", | ||
) | ||
|
||
@classmethod | ||
def _get_yaml_source_paths(cls) -> list[str]: | ||
config_paths = os.environ.get("CONFIG_PATH", None) | ||
|
||
if not config_paths: | ||
return [] | ||
|
||
return [path.strip() for path in config_paths.split(",")] | ||
|
||
@classmethod | ||
def _get_yaml_sources(cls) -> list[pydantic_settings.YamlConfigSettingsSource]: | ||
return [ | ||
pydantic_settings.YamlConfigSettingsSource( | ||
cls, | ||
yaml_file=yaml_file, | ||
) | ||
for yaml_file in cls._get_yaml_source_paths() | ||
] | ||
|
||
@classmethod | ||
def settings_customise_sources( | ||
cls, | ||
settings_cls: typing.Type[pydantic_settings.BaseSettings], | ||
init_settings: pydantic_settings.PydanticBaseSettingsSource, | ||
env_settings: pydantic_settings.PydanticBaseSettingsSource, | ||
dotenv_settings: pydantic_settings.PydanticBaseSettingsSource, | ||
file_secret_settings: pydantic_settings.PydanticBaseSettingsSource, | ||
) -> tuple[pydantic_settings.PydanticBaseSettingsSource, ...]: | ||
return ( | ||
env_settings, | ||
*cls._get_yaml_sources(), | ||
dotenv_settings, | ||
file_secret_settings, | ||
init_settings, | ||
) | ||
|
||
|
||
__all__ = [ | ||
"BaseSettings", | ||
"BaseRootSettings", | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
import typing | ||
|
||
import pydantic | ||
import pydantic._internal._model_construction as pydantic_model_construction | ||
import pydantic.fields | ||
|
||
import dl_settings.base.settings as base_settings | ||
|
||
|
||
class TypedMeta(pydantic_model_construction.ModelMetaclass): | ||
def __init__(cls, name: str, bases: tuple[type, ...], attrs: dict[str, typing.Any]): | ||
cls._classes: dict[str, type[base_settings.BaseSettings]] = {} | ||
|
||
|
||
class TypedBaseModel(base_settings.BaseSettings, metaclass=TypedMeta): | ||
type: str = pydantic.Field(alias="type") | ||
|
||
@classmethod | ||
def __get_type_field_name(cls) -> str: | ||
alias = cls.model_fields["type"].alias | ||
|
||
if alias is None: | ||
raise ValueError("Field 'type' must have alias") | ||
|
||
return alias | ||
|
||
@classmethod | ||
def register(cls, name: str, class_: typing.Type) -> None: | ||
if name in cls._classes: | ||
raise ValueError(f"Class with name '{name}' already registered") | ||
|
||
if not issubclass(class_, cls): | ||
raise ValueError(f"Class '{class_}' must be subclass of '{cls}'") | ||
|
||
cls._classes[name] = class_ | ||
|
||
@classmethod | ||
def factory(cls, data: typing.Any) -> base_settings.BaseSettings: | ||
if isinstance(data, base_settings.BaseSettings): | ||
return data | ||
|
||
if not isinstance(data, dict): | ||
raise ValueError("Data must be dict") | ||
|
||
class_name = data[cls.__get_type_field_name()] | ||
if class_name not in cls._classes: | ||
raise ValueError(f"Unknown type: {class_name}") | ||
|
||
class_ = cls._classes[class_name] | ||
|
||
return class_.model_validate(data) | ||
|
||
@classmethod | ||
def list_factory(cls, data: list[typing.Any]) -> typing.List[base_settings.BaseSettings]: | ||
if not isinstance(data, list): | ||
raise ValueError("Data must be sequence for list factory") | ||
|
||
return [cls.factory(item) for item in data] | ||
|
||
@classmethod | ||
def dict_factory(cls, data: dict[str, typing.Any]) -> typing.Dict[str, base_settings.BaseSettings]: | ||
if not isinstance(data, dict): | ||
raise ValueError("Data must be mapping for dict factory") | ||
|
||
import logging | ||
|
||
logging.error(data) | ||
|
||
return {key: cls.factory(value) for key, value in data.items()} | ||
|
||
|
||
TypedBaseModelT = typing.TypeVar("TypedBaseModelT", bound=TypedBaseModel) | ||
|
||
|
||
if typing.TYPE_CHECKING: | ||
TypedAnnotation = typing.Annotated[TypedBaseModelT, ...] | ||
TypedListAnnotation = typing.Annotated[list[TypedBaseModelT], ...] | ||
TypedDictAnnotation = typing.Annotated[dict[str, TypedBaseModelT], ...] | ||
else: | ||
|
||
class TypedAnnotation: | ||
def __class_getitem__(cls, base_class: TypedBaseModelT) -> typing.Any: | ||
return typing.Annotated[ | ||
pydantic.SerializeAsAny[base_class], | ||
pydantic.BeforeValidator(base_class.factory), | ||
] | ||
|
||
class TypedListAnnotation: | ||
def __class_getitem__(cls, base_class: TypedBaseModelT) -> typing.Any: | ||
return typing.Annotated[ | ||
list[pydantic.SerializeAsAny[base_class]], | ||
pydantic.BeforeValidator(base_class.list_factory), | ||
] | ||
|
||
class TypedDictAnnotation: | ||
def __class_getitem__(cls, base_class: TypedBaseModelT) -> typing.Any: | ||
return typing.Annotated[ | ||
dict[str, pydantic.SerializeAsAny[base_class]], | ||
pydantic.BeforeValidator(base_class.dict_factory), | ||
] | ||
|
||
|
||
__all__ = [ | ||
"TypedBaseModel", | ||
"TypedAnnotation", | ||
"TypedListAnnotation", | ||
"TypedDictAnnotation", | ||
] |
File renamed without changes.
Oops, something went wrong.