Skip to content

Commit

Permalink
fix: Further fixes to typing
Browse files Browse the repository at this point in the history
chore(project): Remove outdated BaseModel variation of SchemanticMixin
chore: Update Justfile wrt recent changes to developer paradigm
documentation: Update README.md to reflect the changes from the overhaul!
chore(version): Bump PATCH
  • Loading branch information
caniko committed Nov 20, 2024
1 parent 90e4194 commit b25a0c3
Show file tree
Hide file tree
Showing 10 changed files with 47 additions and 53 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:

strategy:
matrix:
python-version: [ "3.10", "3.11" ]
python-version: [ "3.10", "3.11", "3.12", "3.12" ]

steps:
- uses: actions/checkout@v3
Expand Down
5 changes: 4 additions & 1 deletion Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,7 @@ format:
@echo "Formatting complete 🎉"

tcheck:
poetry run pyright schemantic
mypy -p schemantic

test:
poetry run pytest tests
29 changes: 14 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,30 +1,31 @@
# Schemantic

Create schemas from models or classes with homologous, grouped, or cultured paradigms.
Do you have configurations stored in files? You have come to the right place! Create schemas from models or classes with homologous, grouped, or cultured paradigms. Batteries for loading and dumping included!

Best with `pydantic.BaseModel` instances, but works with any Python class and `dataclass`/`pydantic.dataclass`!
Supports `pydantic.BaseModel` and any Python class and `dataclass`/`pydantic.dataclass`!

## Classes of schemas

### Homolog
### Homologue
```python
from pydantic import BaseModel
from ordered_set import OrderedSet
from schemantic.schema import HomologSchema
from schemantic import HomologueSchemer

class Thief(BaseModel):
stolen_goods: int
steals_only: str

my_homolog = HomologSchema.from_model(Thief, instance_names=OrderedSet(["copycat", "pink_panther"]))
my_homolog = HomologueSchemer.from_originating_type(Thief, instance_name_to_pre_definition={"copycat": {}, "pink_panther": {}})
```

`HomologueSchemer` also accepts `instance_name_getter` parameter, which is a dictionary of instance names to pre-defined values.

### Grouped
You can manage multiple schemas as a group:

```python
from pydantic import BaseModel
from schemantic.schema import GroupSchema
from schemantic import GroupSchemer

class Baker(BaseModel):
baked_goods: int
Expand All @@ -34,24 +35,24 @@ class Cop(BaseModel):
years_of_service: int
citizen_of: str

group_schema = GroupSchema.from_originating_types([Baker, Cop], )
group_schema = GroupSchemer.from_originating_types([Baker, Cop])
```

### Culture
You can also manage multiple types of schemas under one culture:

```python
from ordered_set import OrderedSet
from schemantic.schema import CultureSchema
from schemantic import CultureSchemer

CultureSchema(source_schemas=OrderedSet([homolog_schema, group_schema]))
CultureSchemer(source_schemas=OrderedSet([homolog_schema, group_schema]))
```

## Methods
`HomologSchema`, `GroupSchema`, and `CultureSchema` have the following methods.
`HomologueSchemer`, `GroupSchemer`, and `CultureSchemer` have the following methods.

### `.schema()`
Creates a dictionary, which represents the schema of the origin class/model.
Creates a BaseModel derived class instance, which represents the schema of the origin class/model.

```python
my_homolog.schema()
Expand Down Expand Up @@ -153,9 +154,7 @@ class TestModel(SchemanticProjectModelMixin, BaseModel):
exclude_me: Optional[int] = None
_exclude_me_too: Optional[float] = None
@classmethod # type: ignore[misc]
@computed_field(return_type=set[str])
@property
@classmethod
def fields_to_exclude_from_single_schema(cls) -> set[str]:
upstream = super().fields_to_exclude_from_single_schema
upstream.update(("exclude_me",))
Expand Down
12 changes: 6 additions & 6 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 9 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
[tool.poetry]
name = "schemantic"
version = "2.0.2"
version = "2.0.3"
description = "Manipulate model schemas utilizing homologous, grouped, or cultured paradigms"
authors = ["caniko <[email protected]>"]
license = "BSD-4"
readme = "README.md"
classifiers = [
"Topic :: Software Development :: Libraries :: Python Modules",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Topic :: Scientific/Engineering :: Information Analysis",
"Operating System :: OS Independent",
]

[tool.poetry.dependencies]
python = ">=3.10,<4.0"
Expand Down
20 changes: 3 additions & 17 deletions schemantic/project.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
from typing import Any, ClassVar

from pydantic import BaseModel


class SchemanticProjectMixin:
include_private: ClassVar[bool] = False
Expand All @@ -14,13 +12,14 @@ def fields_to_exclude_from_single_schema(cls) -> set[str]:
Usage:
@classmethod
@property
def fields_to_exclude_from_single_schema(cls) -> set[str]:
upstream = super().fields_to_exclude_from_single_schema()
upstream.update(("SOME", "FIELD"))
return upstream
:return:
Notice `upstream = super().fields_to_exclude_from_single_schema()`, it ensures
that we can inherit the exclusion fields from the parent class. You don't need
to do this on your root class; moreover, downstream classes could benefit from it.
"""
return set()

Expand All @@ -30,16 +29,3 @@ def single_schema_kwargs(cls) -> dict[str, Any]:
include_private=cls.include_private,
fields_to_exclude=cls.fields_to_exclude_from_single_schema(),
)


SchemanticProjectType = type[SchemanticProjectMixin]


class SchemanticProjectModelMixin(BaseModel, SchemanticProjectMixin):
@classmethod
def fields_to_exclude_from_single_schema(cls) -> set[str]:
return super().fields_to_exclude_from_single_schema()

@classmethod
def single_schema_kwargs(cls) -> dict[str, Any]:
return super().single_schema_kwargs()
8 changes: 4 additions & 4 deletions schemantic/schema/functional.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ def class_arg_alias_to_type_string(
) -> SignatureModel:
constructor_signature = inspect.signature(source_cls.__init__)

result = dict(required={}, optional={})
result = SignatureModel()

type_hints = get_type_hints(source_cls.__init__)
for param_name, param in constructor_signature.parameters.items():
Expand All @@ -87,15 +87,15 @@ def class_arg_alias_to_type_string(
arg_type = native_fallback_get(type_hint.__name__)

if param.default == inspect.Parameter.empty:
result[REQUIRED_MAPPING_KEY][param_name] = InitArgTypeInfo(
result.required[param_name] = InitArgTypeInfo(
type_hint=arg_type, owner_to_default={source_cls: None}
)
else:
result[OPTIONAL_MAPPING_KEY][param_name] = InitArgTypeInfo(
result.optional[param_name] = InitArgTypeInfo(
type_hint=arg_type, owner_to_default={source_cls: param.default}
)

return SignatureModel(**result)
return result


def _fallback_get(source: dict, key: str) -> str:
Expand Down
8 changes: 4 additions & 4 deletions schemantic/schema/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@


class ClassNameMixin(BaseModel):
class_name: str = ...
class_name: str


class DefiningMixin(BaseModel):
Expand Down Expand Up @@ -95,13 +95,13 @@ class SingleSchema(DefiningSignatureModel, ClassNameMixin):

class HomologueSchema(ClassNameMixin):
common: dict = Field(default_factory=dict)
instances: Annotated[dict[str, SchemaDefinition], Doc("The definition of each class instance")] = ...
init_signature: SignatureModel = ...
instances: Annotated[dict[str, SchemaDefinition], Doc("The definition of each class instance")]
init_signature: SignatureModel


class GroupSchema(BaseModel):
common: CommonSignatureModel = Field(default_factory=CommonSignatureModel)
members: Annotated[dict[str, GroupMemberSchema], Doc("The definition of each group member")] = ...
members: Annotated[dict[str, GroupMemberSchema], Doc("The definition of each group member")]
argument_to_typing: ArgNameToTypeInfo


Expand Down
2 changes: 0 additions & 2 deletions schemantic/utils/mapping.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ def update_assert_disjoint(dict_a: dict, dict_b: dict, error_msg_add: Optional[s
"""
Detects key collisions between two dictionaries.
:param dict_a: The first dictionary.
:param dict_b: The second dictionary.
:raises ValueError: If a key collision is detected.
"""
if any(k in dict_a and dict_a[k] != dict_a[k] for k in dict_b):
Expand Down
4 changes: 2 additions & 2 deletions tests/test_schema_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@

from pydantic import BaseModel

from schemantic.project import SchemanticProjectModelMixin
from schemantic.project import SchemanticProjectMixin
from tests.model import infer_expected_schemas
from tests.test_case.main import AbstractTestCulture, AbstractTestGroup, AbstractTestHomologue, AbstractTestSingle


class TestModel(SchemanticProjectModelMixin, BaseModel):
class TestModel(SchemanticProjectMixin, BaseModel):
must_be: int
we: str = "n"

Expand Down

0 comments on commit b25a0c3

Please sign in to comment.