diff --git a/.github/Taskfile.yml b/.github/Taskfile.yml new file mode 100644 index 00000000..9dc1d0c7 --- /dev/null +++ b/.github/Taskfile.yml @@ -0,0 +1,46 @@ +version: "3" + +silent: false + +vars: + VERSION_FILE: ./__version__.txt + RELEASE_NOTE_FILE: ./__release_notes__.md + RELEASE_BRANCH: "master" + CURRENT_BRANCH: + sh: git rev-parse --symbolic-full-name --abbrev-ref HEAD + RELEASE_COMMIT_FILES: "pyproject.toml CHANGELOG.md" + +tasks: + default: + preconditions: + - sh: which hub + msg: hub not found + - sh: "[ {{.CURRENT_BRANCH}} = {{.RELEASE_BRANCH}} ]" + msg: "Please switch to {{.RELEASE_BRANCH}} to create a release" + cmds: + - task: prepare-workspace + - task: publish-release + - task: clean + + prepare-workspace: + cmds: + - poetry run python .github/release.py + publish-release: + vars: + RELEASE_NOTE: + sh: cat {{.RELEASE_NOTE_FILE}} + NEW_VERSION: + sh: cat {{.VERSION_FILE}} + cmds: + - git add {{.RELEASE_COMMIT_FILES}} + - git commit -m "Release {{.NEW_VERSION}} 🚀" + - git push + - git tag v{{.NEW_VERSION}} + - git push --tags + - hub release create -d -t {{.RELEASE_BRANCH}} -m v{{.NEW_VERSION}} -m "$(cat {{.RELEASE_NOTE_FILE}} )" v{{.NEW_VERSION}} + - poetry publish --build + + clean: + cmds: + - rm -f {{.VERSION_FILE}} + - rm -f {{.RELEASE_NOTE_FILE}} diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..0d366967 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,11 @@ +version: 2 +updates: + - package-ecosystem: pip + directory: "/" + schedule: + interval: daily + time: "04:00" + + open-pull-requests-limit: 10 + commit-message: + prefix: "⬆️" diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 42ebf5b6..0d44b2fa 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -3,6 +3,7 @@ on: release: types: - published + - released - edited workflow_dispatch: diff --git a/CHANGELOG.md b/CHANGELOG.md index fb58a9c0..177ad8d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.3.1] - 2020-11-16 + +#### Added + +- Add `schema_extra` config option ([#41](https://github.com/art049/odmantic/pull/41) by [@art049](https://github.com/art049)) + +#### Fixed + +- Fix `setattr` error on a manually initialized EmbeddedModel ([#40](https://github.com/art049/odmantic/pull/40) by [@art049](https://github.com/art049)) + ## [0.3.0] - 2020-11-09 #### Deprecated @@ -15,7 +25,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 #### Added -- Allow parsing document with unset fields defaults ([#28](https://github.com/art049/odmantic/pull/28) by [@art049](https://github.com/art049)) +- Allow parsing document with unset fields defaults ([documentation](https://art049.github.io/odmantic/raw_query_usage/#advanced-parsing-behavior)) ([#28](https://github.com/art049/odmantic/pull/28) by [@art049](https://github.com/art049)) - Integration with Pydantic `Config` class ([#37](https://github.com/art049/odmantic/pull/37) by [@art049](https://github.com/art049)): @@ -66,4 +76,5 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [0.2.0]: https://github.com/art049/odmantic/compare/v0.1.0...v0.2.0 [0.2.1]: https://github.com/art049/odmantic/compare/v0.2.0...v0.2.1 [0.3.0]: https://github.com/art049/odmantic/compare/v0.2.1...v0.3.0 -[unreleased]: https://github.com/art049/odmantic/compare/v0.3.0...HEAD +[0.3.1]: https://github.com/art049/odmantic/compare/v0.3.0...v0.3.1 +[unreleased]: https://github.com/art049/odmantic/compare/v0.3.1...HEAD diff --git a/README.md b/README.md index 03ab8819..9f128a60 100644 --- a/README.md +++ b/README.md @@ -22,9 +22,10 @@ --- -ODMantic is an Object Document Mapper (a kind of ORM but for NoSQL databases) for -MongoDB based on standard python type hints. It's built on top of -pydantic for model definition and validation. +Asynchronous ODM(Object Document Mapper) for MongoDB based on standard python type hints. It's built on top of pydantic for model +definition and validation. Core features: @@ -32,12 +33,14 @@ Core features: using python comparison operators - **Developer experience**: field/method autocompletion, type hints, data validation, - perform database operations in a functional way + perform database operations with a functional API - **Fully typed**: leverage static analysis to reduce runtime issues - **AsyncIO**: works well with ASGI frameworks (FastAPI, FastAPI, quart, sanic, Starlette, ...) - **Serialization**: built in JSON serialization and JSON schema generation diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 65ef752e..7e828d98 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -2,7 +2,7 @@ site_name: ODMantic site_description: AsyncIO MongoDB ODM (Object Document Mapper) using python type hinting repo_name: art049/odmantic repo_url: https://github.com/art049/odmantic - +site_url: https://art049.github.io/odmantic/ strict: true docs_dir: . site_dir: ../site diff --git a/docs/modeling.md b/docs/modeling.md index 8c387a16..ce15c717 100644 --- a/docs/modeling.md +++ b/docs/modeling.md @@ -80,6 +80,12 @@ in the model body. Default: name of the model class +`#!python schema_extra: dict` *(inherited from Pydantic)* + : A dict used to extend/update the generated JSON Schema, or a callable to + post-process it. See [Pydantic: Schema customization](https://pydantic-docs.helpmanual.io/usage/schema/#schema-customization){:target=_blank} for more details. + + Default: `#!python {}` + `#!python anystr_strip_whitespace: bool` *(inherited from Pydantic)* : Whether to strip leading and trailing whitespaces for str & byte types. diff --git a/odmantic/config.py b/odmantic/config.py index dc311862..17eda168 100644 --- a/odmantic/config.py +++ b/odmantic/config.py @@ -1,7 +1,7 @@ import json -from typing import Any, Callable, Dict, Optional, Type +from typing import Any, Callable, Dict, Optional, Type, Union -from pydantic.main import BaseConfig +from pydantic.main import BaseConfig, SchemaExtraCallable from pydantic.typing import AnyCallable from odmantic.bson import BSON_TYPES_ENCODERS @@ -19,6 +19,7 @@ class BaseODMConfig: # Inherited from pydantic title: Optional[str] = None json_encoders: Dict[Type[Any], AnyCallable] = {} + schema_extra: Union[Dict[str, Any], "SchemaExtraCallable"] = {} anystr_strip_whitespace: bool = False json_loads: Callable[[str], Any] = json.loads json_dumps: Callable[..., str] = json.dumps diff --git a/odmantic/model.py b/odmantic/model.py index b5a496d9..bb042f1f 100644 --- a/odmantic/model.py +++ b/odmantic/model.py @@ -472,6 +472,10 @@ class _BaseODMModel(pydantic.BaseModel, metaclass=ABCMeta): __slots__ = ("__fields_modified__",) + def __init__(self, **data: Any): + super().__init__(**data) + object.__setattr__(self, "__fields_modified__", set(self.__odm_fields__.keys())) + @classmethod def validate(cls: Type[TBase], value: Any) -> TBase: if isinstance(value, cls): @@ -617,16 +621,6 @@ class Model(_BaseODMModel, metaclass=ModelMetaclass): id: Union[ObjectId, Any] # TODO fix basic id field typing - def __init__(__odmantic_self__, **data: Any): - super().__init__(**data) - # Uses something other than `self` the first arg to allow "self" as a settable - # attribute - object.__setattr__( - __odmantic_self__, - "__fields_modified__", - set(__odmantic_self__.__odm_fields__.keys()), - ) - def __setattr__(self, name: str, value: Any) -> None: if name == self.__primary_field__: # TODO implement diff --git a/pyproject.toml b/pyproject.toml index caa34034..45f65787 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "ODMantic" -version = "0.3.0" +version = "0.3.1" description = "ODMantic, an AsyncIO MongoDB Object Document Mapper for Python using type hints " authors = ["Arthur Pastel "] license = "ISC License" @@ -37,11 +37,11 @@ classifiers = [ include = ["LICENSE", "README.md"] [tool.poetry.dependencies] -python = ">=3.6 <4.0" +python = ">=3.6,<4.0" importlib-metadata = { version = "^1.0", python = "<3.8" } typing-extensions = { version = "^3.7.4.3", python = "<3.8" } -pydantic = ">=1.6.0 < 1.8.0" -motor = ">=2.1.0 <2.4.0" +pydantic = ">=1.6.0,< 1.8.0" +motor = ">=2.1.0,<2.4.0" fastapi = { version = "^0.61.1", optional = true } [tool.poetry.extras] diff --git a/tests/unit/test_model_logic.py b/tests/unit/test_model_logic.py index 1b013899..d897c379 100644 --- a/tests/unit/test_model_logic.py +++ b/tests/unit/test_model_logic.py @@ -32,6 +32,14 @@ class M(Model): assert instance.__fields_modified__ == set(["f", "id"]) +def test_fields_embedded_modified_no_modification(): + class M(EmbeddedModel): + f: int + + instance = M(f=0) + assert instance.__fields_modified__ == set(["f"]) + + def test_fields_modified_with_default(): class M(Model): f: int = 5 @@ -40,13 +48,15 @@ class M(Model): assert instance.__fields_modified__ == set(["f", "id"]) -def test_fields_modified_one_update(): - class M(Model): +@pytest.mark.parametrize("model_cls", [Model, EmbeddedModel]) +def test_fields_modified_one_update(model_cls): + class M(model_cls): # type: ignore f: int instance = M(f=0) + instance.__fields_modified__.clear() instance.f = 1 - assert instance.__fields_modified__ == set(["f", "id"]) + assert instance.__fields_modified__ == set(["f"]) def test_field_update_with_invalid_data_type():