generated from daniil-berg/boilerplate-py
Compare commits
12 Commits
fe5decad4f
...
master
Author | SHA1 | Date | |
---|---|---|---|
3f0e3db427
|
|||
862a517018
|
|||
3c16b4ebd6
|
|||
0e443f08b9
|
|||
2ca7ccde4a
|
|||
a7caa876c4
|
|||
da244e15ba
|
|||
485aea005e
|
|||
d272864e44
|
|||
4dd1fbaf53
|
|||
4878d550fe
|
|||
2a5e35b334
|
34
.github/workflows/ci.yaml
vendored
Normal file
34
.github/workflows/ci.yaml
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: master
|
||||
tags: 'v*.*.*'
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Test
|
||||
uses: daniil-berg/reusable-workflows/.github/workflows/python-test.yaml@v0.2.1
|
||||
with:
|
||||
versions: '["3.9", "3.10", "3.11"]'
|
||||
unittest-command: 'scripts/test.sh'
|
||||
coverage-command: 'scripts/cov.sh'
|
||||
unittest-requirements: "-e '.[dev]'"
|
||||
typecheck-command: 'scripts/typecheck.sh'
|
||||
typecheck-requirements: '-Ur requirements/dev.txt'
|
||||
typecheck-all-versions: true
|
||||
lint-command: 'scripts/lint.sh'
|
||||
lint-requirements: '-Ur requirements/dev.txt'
|
||||
|
||||
release:
|
||||
name: Release
|
||||
if: ${{ github.ref_type == 'tag' }}
|
||||
needs: test
|
||||
uses: daniil-berg/reusable-workflows/.github/workflows/python-release.yaml@v0.2.1
|
||||
with:
|
||||
git-ref: ${{ github.ref_name }}
|
||||
secrets:
|
||||
release-token: ${{ secrets.TOKEN_GITHUB_CREATE_RELEASE }}
|
||||
publish-token: ${{ secrets.TOKEN_PYPI_PROJECT }}
|
||||
permissions:
|
||||
contents: write
|
4
.gitignore
vendored
4
.gitignore
vendored
@ -21,5 +21,5 @@ __pycache__/
|
||||
# Testing:
|
||||
/.coverage
|
||||
|
||||
# mypy:
|
||||
.mypy_cache/
|
||||
# Miscellaneous cache:
|
||||
.cache/
|
||||
|
@ -10,19 +10,18 @@
|
||||
|
||||
---
|
||||
|
||||
Extension for <a href="https://github.com/marshmallow-code/marshmallow" target="_blank">`marshmallow`</a> to make <a href="https://marshmallow.readthedocs.io/en/stable/quickstart.html#deserializing-to-objects" target="_blank">deserialization to objects</a> easier and improve type safety.
|
||||
Extension for <a href="https://github.com/marshmallow-code/marshmallow" target="_blank">**`marshmallow`**</a> to make <a href="https://marshmallow.readthedocs.io/en/stable/quickstart.html#deserializing-to-objects" target="_blank">deserialization to objects</a> easier and improve type safety.
|
||||
|
||||
The main `GenericSchema` class extends <a href="https://marshmallow.readthedocs.io/en/stable/marshmallow.schema.html#marshmallow.schema.Schema" target="_blank">`marshmallow.Schema`</a> making it **generic** in terms of the class that data should be deserialized to, when calling <a href="https://marshmallow.readthedocs.io/en/stable/marshmallow.schema.html#marshmallow.schema.Schema.load" target="_blank">`load`/`loads`</a>.
|
||||
|
||||
With `GenericSchema` there is no need to explicitly write `post_load` hooks to initialize the object anymore. 🎉
|
||||
|
||||
If the "model" class is (for example) `User`, it just needs to be passed as the type argument, when subclassing `GenericSchema`. Depending on whether `many` is `True` or not, the output of the `load`/`loads` method will then be automatically inferred as either `User` or `list[User]` by any competent type checker. ✨
|
||||
If the "model" class is (for example) `User`, it just needs to be passed as the type argument, when subclassing `GenericSchema`. The output of the `load`/`loads` method will then be automatically inferred as either `User` or `list[User]` (depending on whether `many` is `True` or not) by any competent type checker. ✨
|
||||
|
||||
## Usage Example
|
||||
|
||||
```python
|
||||
from marshmallow import fields
|
||||
from marshmallow_generic import GenericSchema
|
||||
from marshmallow_generic import GenericSchema, fields
|
||||
|
||||
|
||||
class User:
|
||||
|
@ -20,7 +20,7 @@ keywords = [
|
||||
]
|
||||
license = { text = "Apache Software License Version 2.0" }
|
||||
classifiers = [
|
||||
"Development Status :: 4 - Beta",
|
||||
"Development Status :: 5 - Production/Stable",
|
||||
"Programming Language :: Python :: 3 :: Only",
|
||||
"Programming Language :: Python :: 3.9",
|
||||
"Programming Language :: Python :: 3.10",
|
||||
@ -37,34 +37,32 @@ dynamic = [
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
full = [
|
||||
|
||||
]
|
||||
dev = [
|
||||
"black",
|
||||
"build",
|
||||
"coverage[toml]",
|
||||
"isort",
|
||||
"mkdocs-material",
|
||||
"mkdocstrings[python]",
|
||||
"mypy",
|
||||
"ruff",
|
||||
"black==23.3.0",
|
||||
"build==0.10.0",
|
||||
"coverage[toml]==7.2.3",
|
||||
"isort==5.12.0",
|
||||
"mkdocs-material==9.1.6",
|
||||
"mkdocstrings[python]==0.21.2",
|
||||
"mypy==1.2.0",
|
||||
"ruff==0.0.262",
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
repository = "https://github.com/daniil-berg/marshmallow-generic"
|
||||
bug_tracker = "https://github.com/daniil-berg/marshmallow-generic/issues"
|
||||
documentation = "http://daniil-berg.github.io/marshmallow-generic"
|
||||
"Repository" = "https://github.com/daniil-berg/marshmallow-generic"
|
||||
"Issue Tracker" = "https://github.com/daniil-berg/marshmallow-generic/issues"
|
||||
"Documentation" = "http://daniil-berg.github.io/marshmallow-generic"
|
||||
|
||||
[tool.setuptools.dynamic]
|
||||
dependencies = { file = "requirements/common.txt" }
|
||||
readme = { file = ["README.md"] }
|
||||
version = {attr = "marshmallow_generic.__version__"}
|
||||
readme = { file = ["README.md"], content-type = "text/markdown" }
|
||||
version = { attr = "marshmallow_generic.__version__" }
|
||||
|
||||
#########################
|
||||
# Static type checking: #
|
||||
|
||||
[tool.mypy]
|
||||
cache_dir = ".cache/mypy"
|
||||
files = [
|
||||
"src/",
|
||||
"tests/",
|
||||
@ -80,6 +78,7 @@ plugins = [
|
||||
# Unit test coverage: #
|
||||
|
||||
[tool.coverage.run]
|
||||
data_file = ".cache/coverage"
|
||||
source = [
|
||||
"src/",
|
||||
]
|
||||
@ -105,6 +104,7 @@ omit = [
|
||||
# Linting and style checking: #
|
||||
|
||||
[tool.ruff]
|
||||
cache-dir = ".cache/ruff"
|
||||
select = [
|
||||
"E", # pycodestyle errors
|
||||
"W", # pycodestyle warnings
|
||||
|
@ -1 +1 @@
|
||||
marshmallow
|
||||
marshmallow>=3.12.0
|
@ -1,9 +1,9 @@
|
||||
-r common.txt
|
||||
black
|
||||
build
|
||||
coverage[toml]
|
||||
isort
|
||||
mkdocs-material
|
||||
mkdocstrings[python]
|
||||
mypy
|
||||
ruff
|
||||
black==23.3.0
|
||||
build==0.10.0
|
||||
coverage[toml]==7.2.3
|
||||
isort==5.12.0
|
||||
mkdocs-material==9.1.6
|
||||
mkdocstrings[python]==0.21.2
|
||||
mypy==1.2.0
|
||||
ruff==0.0.262
|
||||
|
12
scripts/ci.sh
Executable file
12
scripts/ci.sh
Executable file
@ -0,0 +1,12 @@
|
||||
#!/usr/bin/env bash
|
||||
# Runs full CI pipeline (test, typecheck, lint).
|
||||
|
||||
typeset scripts_dir="$(dirname $(realpath $0))"
|
||||
|
||||
source "${scripts_dir}/util.sh"
|
||||
|
||||
"${scripts_dir}/test.sh"
|
||||
"${scripts_dir}/typecheck.sh"
|
||||
"${scripts_dir}/lint.sh"
|
||||
|
||||
echo -e "${background_black}${bold_green}✅ 🎉 All checks passed!${color_reset}"
|
10
scripts/cov.sh
Executable file
10
scripts/cov.sh
Executable file
@ -0,0 +1,10 @@
|
||||
#!/usr/bin/env bash
|
||||
# Runs unit tests.
|
||||
# If successful, prints only the coverage percentage.
|
||||
# If an error occurs, prints the entire unit tests progress output.
|
||||
|
||||
source "$(dirname $(realpath $0))/util.sh"
|
||||
|
||||
coverage erase
|
||||
run_and_capture coverage run
|
||||
coverage report | awk '$1 == "TOTAL" {print $NF; exit}'
|
@ -1,18 +1,17 @@
|
||||
#!/usr/bin/env bash
|
||||
# Runs type checker and linters.
|
||||
# Runs various linters.
|
||||
|
||||
# Ensure that we return to the current working directory
|
||||
# and exit the script immediately in case of an error:
|
||||
trap "cd $(realpath ${PWD}); exit 1" ERR
|
||||
# Change into project root directory:
|
||||
cd "$(dirname $(dirname $(realpath $0)))"
|
||||
|
||||
echo 'Performing type checks...'
|
||||
mypy
|
||||
echo
|
||||
source "$(dirname $(realpath $0))/util.sh"
|
||||
|
||||
echo 'Linting source and test files...'
|
||||
|
||||
echo ' isort - consistent imports'
|
||||
isort src/ tests/ --check-only
|
||||
|
||||
echo ' ruff - extensive linting'
|
||||
ruff src/ tests/
|
||||
black src/ tests/ --check
|
||||
echo -e 'No issues found.'
|
||||
|
||||
echo ' black - consistent style'
|
||||
run_and_capture black src/ tests/ --check
|
||||
|
||||
echo -e "${bold_green}No issues found${color_reset}\n"
|
||||
|
@ -1,17 +1,12 @@
|
||||
#!/usr/bin/env bash
|
||||
# Runs unit tests and prints only coverage percentage, if successful.
|
||||
# If an error occurs, prints the entire unit tests progress output.
|
||||
# Runs unit tests and reports coverage percentage.
|
||||
|
||||
# Ensure that we return to the current working directory in case of an error:
|
||||
trap "cd $(realpath ${PWD})" ERR
|
||||
# Change into project root directory:
|
||||
cd "$(dirname $(dirname $(realpath $0)))"
|
||||
source "$(dirname $(realpath $0))/util.sh"
|
||||
|
||||
coverage erase
|
||||
# Capture the test progression in a variable:
|
||||
typeset progress
|
||||
progress=$(coverage run 2>&1)
|
||||
# If tests failed or produced errors, write progress/messages to stderr and exit:
|
||||
[[ $? -eq 0 ]] || { >&2 echo "${progress}"; exit 1; }
|
||||
# Otherwise extract the total coverage percentage from the produced report and write it to stdout:
|
||||
coverage report | awk '$1 == "TOTAL" {print $NF; exit}'
|
||||
echo 'Running unit tests...'
|
||||
coverage run
|
||||
typeset percentage
|
||||
typeset color
|
||||
percentage="$(coverage report | awk '$1 == "TOTAL" {print $NF; exit}')"
|
||||
[[ $percentage == "100%" ]] && color="${bold_green}" || color="${yellow}"
|
||||
echo -e "${color}${percentage} coverage${color_reset}\n"
|
||||
|
8
scripts/typecheck.sh
Executable file
8
scripts/typecheck.sh
Executable file
@ -0,0 +1,8 @@
|
||||
#!/usr/bin/env bash
|
||||
# Runs type checker.
|
||||
|
||||
source "$(dirname $(realpath $0))/util.sh"
|
||||
|
||||
echo 'Performing type checks...'
|
||||
mypy
|
||||
echo
|
20
scripts/util.sh
Normal file
20
scripts/util.sh
Normal file
@ -0,0 +1,20 @@
|
||||
run_and_capture() {
|
||||
# Captures stderr of any command passed to it
|
||||
# and releases it only if the command exits with a non-zero code.
|
||||
typeset output
|
||||
output=$($@ 2>&1)
|
||||
typeset exit_status=$?
|
||||
[[ $exit_status == 0 ]] || >&2 echo "${output}"
|
||||
return $exit_status
|
||||
}
|
||||
|
||||
# Ensure that we return to the current working directory
|
||||
# and exit the script immediately in case of an error:
|
||||
trap "cd $(realpath ${PWD}); exit 1" ERR
|
||||
# Change into project root directory:
|
||||
cd "$(dirname $(dirname $(realpath $0)))"
|
||||
|
||||
typeset background_black='\033[40m'
|
||||
typeset bold_green='\033[1;92m'
|
||||
typeset yellow='\033[0;33m'
|
||||
typeset color_reset='\033[0m'
|
@ -13,11 +13,45 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License."""
|
||||
|
||||
__version__ = "0.0.1"
|
||||
__version__ = "1.0.0"
|
||||
|
||||
__doc__ = """
|
||||
Generic schema with full typing support and minimal boilerplate.
|
||||
"""
|
||||
|
||||
from .decorators import post_load
|
||||
from .schema import GenericSchema
|
||||
__all__ = [
|
||||
# Custom:
|
||||
"GenericSchema",
|
||||
"post_load",
|
||||
# Re-exports from marshmallow:
|
||||
"EXCLUDE",
|
||||
"INCLUDE",
|
||||
"RAISE",
|
||||
"Schema",
|
||||
"SchemaOpts",
|
||||
"fields",
|
||||
"validates",
|
||||
"validates_schema",
|
||||
"pre_dump",
|
||||
"post_dump",
|
||||
"pre_load",
|
||||
# "post_load",
|
||||
"pprint",
|
||||
"ValidationError",
|
||||
"missing",
|
||||
]
|
||||
|
||||
from marshmallow import fields
|
||||
from marshmallow.decorators import ( # `post_load` overloaded
|
||||
post_dump,
|
||||
pre_dump,
|
||||
pre_load,
|
||||
validates,
|
||||
validates_schema,
|
||||
)
|
||||
from marshmallow.exceptions import ValidationError
|
||||
from marshmallow.schema import Schema, SchemaOpts
|
||||
from marshmallow.utils import EXCLUDE, INCLUDE, RAISE, missing, pprint
|
||||
|
||||
from marshmallow_generic.decorators import post_load
|
||||
from marshmallow_generic.schema import GenericSchema
|
||||
|
@ -16,6 +16,11 @@ from .decorators import post_load
|
||||
|
||||
Model = TypeVar("Model")
|
||||
|
||||
MANY_SCHEMA_UNSAFE = (
|
||||
"Changing `many` schema-wide breaks type safety. "
|
||||
"Use the the `many` parameter of specific methods (like `load`) instead."
|
||||
)
|
||||
|
||||
|
||||
class GenericSchema(GenericInsightMixin1[Model], Schema):
|
||||
"""
|
||||
@ -54,10 +59,10 @@ class GenericSchema(GenericInsightMixin1[Model], Schema):
|
||||
dump_only: Union[Sequence[str], set[str]] = (),
|
||||
partial: Union[bool, Sequence[str], set[str]] = False,
|
||||
unknown: Optional[str] = None,
|
||||
many: Optional[bool] = None,
|
||||
many: bool = False, # usage discouraged
|
||||
) -> None:
|
||||
"""
|
||||
Emits a warning, if the `many` argument is not `None`.
|
||||
Emits a warning, if the `many` argument is not `False`.
|
||||
|
||||
Otherwise the same as in [`marshmallow.Schema`][marshmallow.Schema].
|
||||
|
||||
@ -72,40 +77,35 @@ class GenericSchema(GenericInsightMixin1[Model], Schema):
|
||||
it is not used. Nested fields can be represented with dot
|
||||
delimiters.
|
||||
context:
|
||||
Optional context passed to [`Method`]
|
||||
[marshmallow.fields.Method] and [`Function`]
|
||||
[marshmallow.fields.Function] fields.
|
||||
Optional context passed to
|
||||
[`Method`][marshmallow.fields.Method] and
|
||||
[`Function`][marshmallow.fields.Function] fields.
|
||||
load_only:
|
||||
Fields to skip during serialization (write-only fields)
|
||||
dump_only:
|
||||
Fields to skip during deserialization (read-only fields)
|
||||
partial:
|
||||
Whether to ignore missing fields and not require any fields
|
||||
declared. Propagates down to [`Nested`]
|
||||
[marshmallow.fields.Nested] fields as well. If its value is an
|
||||
iterable, only missing fields listed in that iterable will be
|
||||
ignored. Use dot delimiters to specify nested fields.
|
||||
declared. Propagates down to
|
||||
[`Nested`][marshmallow.fields.Nested] fields as well. If its
|
||||
value is an iterable, only missing fields listed in that
|
||||
iterable will be ignored. Use dot delimiters to specify nested
|
||||
fields.
|
||||
unknown:
|
||||
Whether to exclude, include, or raise an error for unknown
|
||||
fields in the data. Use `EXCLUDE`, `INCLUDE` or `RAISE`.
|
||||
many:
|
||||
!!! warning
|
||||
Specifying this option schema-wide undermines the type
|
||||
safety that this class aims to provide and passing any
|
||||
value other than `None` will trigger a warning. Use the
|
||||
method-specific `many` parameter, when calling
|
||||
Changing this option schema-wide undermines the type
|
||||
safety that this class aims to provide. Passing `True`
|
||||
will therefore trigger a warning. You should instead use
|
||||
the method-specific `many` parameter, when calling
|
||||
[`dump`][marshmallow_generic.GenericSchema.dump]/
|
||||
[`dumps`][marshmallow_generic.GenericSchema.dumps] or
|
||||
[`load`][marshmallow_generic.GenericSchema.load]/
|
||||
[`loads`][marshmallow_generic.GenericSchema.loads] instead.
|
||||
[`loads`][marshmallow_generic.GenericSchema.loads].
|
||||
"""
|
||||
if many is not None:
|
||||
warn(
|
||||
"Setting `many` schema-wide breaks type safety. Use the the "
|
||||
"`many` parameter of specific methods (like `load`) instead."
|
||||
)
|
||||
else:
|
||||
many = bool(many)
|
||||
self._pre_init = True
|
||||
super().__init__(
|
||||
only=only,
|
||||
exclude=exclude,
|
||||
@ -116,14 +116,27 @@ class GenericSchema(GenericInsightMixin1[Model], Schema):
|
||||
partial=partial,
|
||||
unknown=unknown,
|
||||
)
|
||||
self._pre_init = False
|
||||
|
||||
def __setattr__(self, name: str, value: Any) -> None:
|
||||
"""
|
||||
Warns, when trying to set `many` to anything other than `False`.
|
||||
|
||||
Otherwise the same the normal
|
||||
[`object.__setattr__`](https://docs.python.org/3/reference/datamodel.html#object.__setattr__).
|
||||
"""
|
||||
if name == "many" and value is not False:
|
||||
warn(MANY_SCHEMA_UNSAFE, stacklevel=4 if self._pre_init else 2)
|
||||
super().__setattr__(name, value)
|
||||
|
||||
@post_load
|
||||
def instantiate(self, data: dict[str, Any], **_kwargs: Any) -> Model:
|
||||
"""
|
||||
Unpacks `data` into the constructor of the specified **`Model`**.
|
||||
|
||||
Registered as a [`@post_load`]
|
||||
[marshmallow_generic.decorators.post_load] hook for the schema.
|
||||
Registered as a
|
||||
[`@post_load`][marshmallow_generic.decorators.post_load]
|
||||
hook for the schema.
|
||||
|
||||
!!! warning
|
||||
You should probably not use this method directly. No parsing,
|
||||
@ -169,8 +182,9 @@ class GenericSchema(GenericInsightMixin1[Model], Schema):
|
||||
"""
|
||||
Serializes **`Model`** objects to native Python data types.
|
||||
|
||||
Same as [`marshmallow.Schema.dump`]
|
||||
[marshmallow.schema.Schema.dump] at runtime.
|
||||
Same as
|
||||
[`marshmallow.Schema.dump`][marshmallow.schema.Schema.dump]
|
||||
at runtime.
|
||||
|
||||
Annotations ensure that type checkers will infer the return type
|
||||
correctly based on the `many` argument, and also enforce the `obj`
|
||||
@ -254,10 +268,10 @@ class GenericSchema(GenericInsightMixin1[Model], Schema):
|
||||
"""
|
||||
Deserializes data to objects of the specified **`Model`** class.
|
||||
|
||||
Same as [`marshmallow.Schema.load`]
|
||||
[marshmallow.schema.Schema.load] at runtime, but data will always
|
||||
pass through the [`instantiate`]
|
||||
[marshmallow_generic.schema.GenericSchema.instantiate]
|
||||
Same as
|
||||
[`marshmallow.Schema.load`][marshmallow.schema.Schema.load] at
|
||||
runtime, but data will always pass through the
|
||||
[`instantiate`][marshmallow_generic.schema.GenericSchema.instantiate]
|
||||
hook after deserialization.
|
||||
|
||||
Annotations ensure that type checkers will infer the return type
|
||||
@ -271,11 +285,11 @@ class GenericSchema(GenericInsightMixin1[Model], Schema):
|
||||
the value for `self.many` is used.
|
||||
partial:
|
||||
Whether to ignore missing fields and not require any
|
||||
fields declared. Propagates down to [`Nested`]
|
||||
[marshmallow.fields.Nested] fields as well. If its value
|
||||
is an iterable, only missing fields listed in that
|
||||
iterable will be ignored. Use dot delimiters to specify
|
||||
nested fields.
|
||||
fields declared. Propagates down to
|
||||
[`Nested`][marshmallow.fields.Nested] fields as well. If
|
||||
its value is an iterable, only missing fields listed in
|
||||
that iterable will be ignored. Use dot delimiters to
|
||||
specify nested fields.
|
||||
unknown:
|
||||
Whether to exclude, include, or raise an error for unknown
|
||||
fields in the data. Use `EXCLUDE`, `INCLUDE` or `RAISE`.
|
||||
@ -323,10 +337,10 @@ class GenericSchema(GenericInsightMixin1[Model], Schema):
|
||||
"""
|
||||
Deserializes data to objects of the specified **`Model`** class.
|
||||
|
||||
Same as [`marshmallow.Schema.loads`]
|
||||
[marshmallow.schema.Schema.loads] at runtime, but data will always
|
||||
pass through the [`instantiate`]
|
||||
[marshmallow_generic.schema.GenericSchema.instantiate]
|
||||
Same as
|
||||
[`marshmallow.Schema.loads`][marshmallow.schema.Schema.loads] at
|
||||
runtime, but data will always pass through the
|
||||
[`instantiate`][marshmallow_generic.schema.GenericSchema.instantiate]
|
||||
hook after deserialization.
|
||||
|
||||
Annotations ensure that type checkers will infer the return type
|
||||
@ -340,11 +354,11 @@ class GenericSchema(GenericInsightMixin1[Model], Schema):
|
||||
the value for `self.many` is used.
|
||||
partial:
|
||||
Whether to ignore missing fields and not require any
|
||||
fields declared. Propagates down to [`Nested`]
|
||||
[marshmallow.fields.Nested] fields as well. If its value
|
||||
is an iterable, only missing fields listed in that
|
||||
iterable will be ignored. Use dot delimiters to specify
|
||||
nested fields.
|
||||
fields declared. Propagates down to
|
||||
[`Nested`][marshmallow.fields.Nested] fields as well. If
|
||||
its value is an iterable, only missing fields listed in
|
||||
that iterable will be ignored. Use dot delimiters to
|
||||
specify nested fields.
|
||||
unknown:
|
||||
Whether to exclude, include, or raise an error for unknown
|
||||
fields in the data. Use `EXCLUDE`, `INCLUDE` or `RAISE`.
|
||||
|
@ -19,16 +19,20 @@ class GenericSchemaTestCase(TestCase):
|
||||
"dump_only": object(),
|
||||
"partial": object(),
|
||||
"unknown": object(),
|
||||
"many": None,
|
||||
"many": object(),
|
||||
}
|
||||
schema.GenericSchema[Foo](**kwargs)
|
||||
mock_super_init.assert_called_once_with(**kwargs | {"many": False})
|
||||
mock_super_init.reset_mock()
|
||||
kwargs["many"] = True
|
||||
with self.assertWarns(UserWarning):
|
||||
schema.GenericSchema[Foo](**kwargs)
|
||||
mock_super_init.assert_called_once_with(**kwargs)
|
||||
|
||||
def test___setattr__(self) -> None:
|
||||
class Foo:
|
||||
pass
|
||||
|
||||
obj = schema.GenericSchema[Foo]()
|
||||
with self.assertWarns(UserWarning):
|
||||
obj.many = new = MagicMock()
|
||||
self.assertIs(new, obj.many)
|
||||
|
||||
@patch.object(_util.GenericInsightMixin, "_get_type_arg")
|
||||
def test_instantiate(self, mock__get_type_arg: MagicMock) -> None:
|
||||
mock__get_type_arg.return_value = mock_cls = MagicMock()
|
||||
|
Reference in New Issue
Block a user