pax_global_header00006660000000000000000000000064147501605020014512gustar00rootroot0000000000000052 comment=7a71dfcb7a57fd415c2adb9de8e123d2d5d2fda8 marshmallow-3.26.1/000077500000000000000000000000001475016050200141315ustar00rootroot00000000000000marshmallow-3.26.1/.github/000077500000000000000000000000001475016050200154715ustar00rootroot00000000000000marshmallow-3.26.1/.github/FUNDING.yml000066400000000000000000000000741475016050200173070ustar00rootroot00000000000000open_collective: "marshmallow" tidelift: "pypi/marshmallow" marshmallow-3.26.1/.github/dependabot.yml000066400000000000000000000003301475016050200203150ustar00rootroot00000000000000version: 2 updates: - package-ecosystem: pip directory: "/" schedule: interval: daily open-pull-requests-limit: 10 - package-ecosystem: "github-actions" directory: "/" schedule: interval: "monthly" marshmallow-3.26.1/.github/workflows/000077500000000000000000000000001475016050200175265ustar00rootroot00000000000000marshmallow-3.26.1/.github/workflows/build-release.yml000066400000000000000000000047361475016050200230000ustar00rootroot00000000000000name: build on: push: branches: ["dev", "*.x-line"] tags: ["*"] pull_request: jobs: docs: name: docs runs-on: ubuntu-latest steps: - uses: actions/checkout@v4.0.0 - uses: actions/setup-python@v5 with: python-version: "3.13" - run: pip install tox - run: tox -e docs tests: name: ${{ matrix.name }} runs-on: ubuntu-latest strategy: fail-fast: false matrix: include: - { name: "3.9", python: "3.9", tox: py39 } - { name: "3.13", python: "3.13", tox: py313 } - { name: "mypy", python: "3.13", tox: mypy } steps: - uses: actions/checkout@v4.0.0 - uses: actions/setup-python@v5 with: python-version: ${{ matrix.python }} allow-prereleases: true - run: python -m pip install tox - run: python -m tox -e ${{ matrix.tox }} build: name: Build package runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: "3.13" - name: Install pypa/build run: python -m pip install build - name: Build a binary wheel and a source tarball run: python -m build - name: Install twine run: python -m pip install twine - name: Check build run: python -m twine check --strict dist/* - name: Store the distribution packages uses: actions/upload-artifact@v4 with: name: python-package-distributions path: dist/ # this duplicates pre-commit.ci, so only run it on tags # it guarantees that linting is passing prior to a release lint-pre-release: if: startsWith(github.ref, 'refs/tags') runs-on: ubuntu-latest steps: - uses: actions/checkout@v4.0.0 - uses: actions/setup-python@v5 with: python-version: "3.13" - run: python -m pip install tox - run: python -m tox -e lint publish-to-pypi: name: PyPI release if: startsWith(github.ref, 'refs/tags/') needs: [build, tests, lint-pre-release] runs-on: ubuntu-latest environment: name: pypi url: https://pypi.org/p/marshmallow permissions: id-token: write steps: - name: Download all the dists uses: actions/download-artifact@v4 with: name: python-package-distributions path: dist/ - name: Publish distribution to PyPI uses: pypa/gh-action-pypi-publish@release/v1 marshmallow-3.26.1/.gitignore000066400000000000000000000012511475016050200161200ustar00rootroot00000000000000*.py[cod] # C extensions *.so # Packages *.egg *.egg-info dist build eggs parts bin var sdist develop-eggs .installed.cfg lib lib64 pip-wheel-metadata # Installer logs pip-log.txt # Unit test / coverage reports .coverage htmlcov .tox nosetests.xml .cache .pytest_cache # Translations *.mo # Mr Developer .mr.developer.cfg # IDE .project .pydevproject .idea # Coverage cover .coveragerc # Sphinx docs/_build README.html *.ipynb .ipynb_checkpoints Vagrantfile .vagrant *.db *.ai .konchrc _sandbox pylintrc # Virtualenvs env venv # pyenv .python-version # pytest .pytest_cache # Other .directory *.pprof # mypy .mypy_cache/ .dmypy.json dmypy.json # ruff .ruff_cache marshmallow-3.26.1/.pre-commit-config.yaml000066400000000000000000000010261475016050200204110ustar00rootroot00000000000000ci: autoupdate_schedule: monthly repos: - repo: https://github.com/astral-sh/ruff-pre-commit rev: v0.9.2 hooks: - id: ruff - id: ruff-format - repo: https://github.com/python-jsonschema/check-jsonschema rev: 0.31.0 hooks: - id: check-github-workflows - id: check-readthedocs # TODO: Remove blacken-docs when https://github.com/astral-sh/ruff/issues/8237 is implemented - repo: https://github.com/asottile/blacken-docs rev: 1.19.1 hooks: - id: blacken-docs additional_dependencies: [black==24.10.0] marshmallow-3.26.1/.readthedocs.yml000066400000000000000000000003241475016050200172160ustar00rootroot00000000000000version: 2 sphinx: configuration: docs/conf.py formats: - pdf build: os: ubuntu-22.04 tools: python: "3.13" python: install: - method: pip path: . extra_requirements: - docs marshmallow-3.26.1/AUTHORS.rst000066400000000000000000000234341475016050200160160ustar00rootroot00000000000000******* Authors ******* Leads ===== - Steven Loria `@sloria `_ - Jérôme Lafréchoux `@lafrech `_ - Jared Deckard `@deckar01 `_ Contributors (chronological) ============================ - Sebastian Vetter `@elbaschid `_ - Eduard Carreras `@ecarreras `_ - Joakim Ekberg `@kalasjocke `_ - Mark Grey `@DeaconDesperado `_ - Anders Steinlein `@asteinlein `_ - Cyril Thomas `@Ketouem `_ - Austin Macdonald `@asmacdo `_ - Josh Carp `@jmcarp `_ - `@amikholap `_ - Sven-Hendrik Haase `@svenstaro `_ - Eric Wang `@ewang `_ - `@philtay `_ - `@malexer `_ - Andriy Yurchuk `@Ch00k `_ - Vesa Uimonen `@vesauimonen `_ - David Lord `@davidism `_ - Daniel Castro `@0xDCA `_ - Ben Jones `@RealSalmon `_ - Patrick Woods `@hakjoon `_ - Lukas Heiniger `@3rdcycle `_ - Ryan Lowe `@ryanlowe0 `_ - Jimmy Jia `@taion `_ - `@lustdante `_ - Sergey Aganezov, Jr. `@sergey-aganezov-jr `_ - Kevin Stone `@kevinastone `_ - Alex Morken `@alexmorken `_ - Sergey Polzunov `@traut `_ - Kelvin Hammond `@kelvinhammond `_ - Matt Stobo `@mwstobo `_ - Max Orhai `@max-orhai `_ - Praveen `@praveen-p `_ - Stas Sușcov `@stas `_ - Florian `@floqqi `_ - Evgeny Sureev `@evgeny-sureev `_ - Matt Bachmann `@Bachmann1234 `_ - Daniel Imhoff `@dwieeb `_ - Juan Rossi `@juanrossi `_ - Andrew Haigh `@nelfin `_ - `@Mise `_ - Taylor Edmiston `@tedmiston `_ - Francisco Demartino `@franciscod `_ - Eric Wang `@ewang `_ - Eugene Prikazchikov `@eprikazc `_ - Damian Heard `@DamianHeard `_ - Alec Reiter `@justanr `_ - Dan Sutherland `@d-sutherland `_ - Jeff Widman `@jeffwidman `_ - Simeon Visser `@svisser `_ - Taylan Develioglu `@tdevelioglu `_ - Danilo Akamine `@daniloakamine `_ - Maxim Kulkin `@maximkulkin `_ - `@immerrr `_ - Mike Yumatov `@yumike `_ - Tim Mundt `@Tim-Erwin `_ - Russell Davies `@russelldavies `_ - Jared Deckard `@deckar01 `_ - David Thornton `@davidthornton `_ - Vuong Hoang `@vuonghv `_ - David Bertouille `@dbertouille `_ - Alexandre Bonnetain `@Shir0kamii `_ - Tuukka Mustonen `@tuukkamustonen `_ - Tero Vuotila `@tvuotila `_ - Paul Zumbrun `@pauljz `_ - Gary Wilson Jr. `@gdub `_ - Sabine Maennel `@sabinem `_ - Victor Varvaryuk `@mindojo-victor `_ - Jāzeps Baško `@jbasko `_ - `@podhmo `_ - Dmitry Orlov `@mosquito `_ - Yuri Heupa `@YuriHeupa `_ - Roy Williams `@rowillia `_ - Vlad Frolov `@frol `_ - Erling Børresen `@erlingbo `_ - Jérôme Lafréchoux `@lafrech `_ - Roy Williams `@rowillia `_ - `@dradetsky `_ - Michal Kononenko `@MichalKononenko `_ - Yoichi NAKAYAMA `@yoichi `_ - Bernhard M. Wiedemann `@bmwiedemann `_ - Scott Werner `@scottwernervt `_ - Leonardo Fedalto `@Fedalto `_ - `@sduthil `_ - Steven Sklar `@sklarsa `_ - Alisson Silveira `@4lissonsilveira `_ - Harlov Nikita `@harlov `_ - `@stj `_ - Tomasz Magulski `@magul `_ - Suren Khorenyan `@mahenzon `_ - Jeffrey Berger `@JeffBerger `_ - Felix Yan `@felixonmars `_ - Prasanjit Prakash `@ikilledthecat `_ - Guillaume Gelin `@ramnes `_ - Maxim Novikov `@m-novikov `_ - James Remeika `@remeika `_ - Karandeep Singh Nagra `@knagra `_ - Dushyant Rijhwani `@dushr `_ - Viktor Kerkez `@alefnula `_ - Victor Gavro `@vgavro `_ - Kamil Gałuszka `@galuszkak `_ - David Watson `@arbor-dwatson `_ - Jan Margeta `@jmargeta `_ - AlexV `@asmodehn `_ - `@toffan `_ - Hampus Dunström `@Dunstrom `_ - Robert Jensen `@r1b `_ - Arijit Basu `@sayanarijit `_ - Sanjay P `@snjypl `_ - Víctor Zabalza `@zblz `_ - Riley Gibbs `@rileyjohngibbs `_ - Henry Doupe `@hdoupe `_ - `@miniscruff `_ - `@maxalbert `_ - Kim Gustyr `@khvn26 `_ - Bryce Drennan `@brycedrennan `_ - Tim Shaffer `@timster `_ - Hugo van Kemenade `@hugovk `_ - Maciej Urbański `@rooterkyberian `_ - Kostas Konstantopoulos `@kdop `_ - Stephen J. Fuhry `@fuhrysteve `_ - `@dursk `_ - Ezra MacDonald `@macdonaldezra `_ - Stanislav Rogovskiy `@atmo `_ - Cristi Scoarta `@cristi23 `_ - Anthony Sottile `@asottile `_ - Charles-Axel Dein `@charlax `_ - `@phrfpeixoto `_ - `@jceresini `_ - Nikolay Shebanov `@killthekitten `_ - Taneli Hukkinen `@hukkinj1 `_ - `@Reskov `_ - Albert Tugushev `@atugushev `_ - `@dfirst `_ - Tim Gates `@timgates42 `_ - Nathan `@nbanmp `_ - Ronan Murphy `@Resinderate `_ - Laurie Opperman `@EpicWink `_ - Ram Rachum `@cool-RR `_ - `@weeix `_ - Juan Norris `@juannorris `_ - 장준영 `@jun0jang `_ - `@ebargtuo `_ - Michał Getka `@mgetka `_ - Nadège Michel `@nadege `_ - Tamara `@infinityxxx `_ - Stephen Rosen `@sirosen `_ - Vladimir Mikhaylov `@vemikhaylov `_ - Stephen Eaton `@madeinoz67 `_ - Antonio Lassandro `@lassandroan `_ - Javier Fernández `@jfernandz `_ - Michael Dimchuk `@michaeldimchuk `_ - Jochen Kupperschmidt `@homeworkprod `_ - `@yourun-proger `_ - Ryan Morehart `@traherom `_ - Ben Windsor `@bwindsor `_ - Kevin Kirsche `@kkirsche `_ - Isira Seneviratne `@Isira-Seneviratne `_ - Karthikeyan Singaravelan `@tirkarthi `_ - Marco Satti `@marcosatti `_ - Ivo Reumkens `@vanHoi `_ - Aditya Tewary `@aditkumar72 `_ - Sebastien Lovergne `@TheBigRoomXXL `_ - Peter C `@somethingnew2-0 `_ - Marcel Jackwerth `@mrcljx` `_ - Fares Abubaker `@Fares-Abubaker `_ - Nicolas Simonds `@0xDEC0DE `_ marshmallow-3.26.1/CHANGELOG.rst000066400000000000000000002523011475016050200161550ustar00rootroot00000000000000Changelog --------- 3.26.1 (2025-02-03) ******************* Bug fixes: - Typing: Fix type annotations for `class Meta ` options (:issue:`2804`). Thanks :user:`lawrence-law` for reporting. Other changes: - Remove default value for the ``data`` param of `Nested._deserialize ` (:issue:`2802`). Thanks :user:`gbenson` for reporting. 3.26.0 (2025-01-22) ******************* Features: - Typing: Add type annotations and improved documentation for `class Meta ` options (:pr:`2760`). - Typing: Improve type coverage of `marshmallow.Schema.SchemaMeta` (:pr:`2761`). - Typing: `marshmallow.Schema.loads` parameter allows `bytes` and `bytesarray` (:pr:`2769`). Bug fixes: - Respect ``data_key`` when schema validators raise a `ValidationError ` with a ``field_name`` argument (:issue:`2170`). Thanks :user:`matejsp` for reporting. - Correctly handle multiple `@post_load ` methods where one method appends to the data and another passes ``pass_original=True`` (:issue:`1755`). Thanks :user:`ghostwheel42` for reporting. - ``URL`` fields now properly validate ``file`` paths (:issue:`2249`). Thanks :user:`0xDEC0DE` for reporting and fixing. Documentation: - Add :doc:`upgrading guides ` for 3.24 and 3.26 (:pr:`2780`). - Various documentation improvements (:pr:`2757`, :pr:`2759`, :pr:`2765`, :pr:`2774`, :pr:`2778`, :pr:`2783`, :pr:`2796`). Deprecations: - The ``ordered`` `class Meta ` option is deprecated (:issue:`2146`, :pr:`2762`). Field order is already preserved by default. Set `marshmallow.Schema.dict_class` to `collections.OrderedDict` to maintain the previous behavior. 3.25.1 (2025-01-11) ******************* Bug fixes: - Typing: Fix type annotations for `Tuple `, `Boolean `, and `Pluck ` constructors (:pr:`2756`). - Typing: Fix overload for `marshmallow.class_registry.get_class` (:pr:`2756`). Documentation: - Various documentation improvements (:pr:`2746`, :pr:`2747`, :pr:`2748`, :pr:`2749`, :pr:`2750`, :pr:`2751`). 3.25.0 (2025-01-09) ******************* Features: - Typing: Improve type annotations for ``SchemaMeta.get_declared_fields`` (:pr:`2742`). Bug fixes: - Typing: Relax type annotation for ``Schema.opts`` to allow subclasses to define their own options classes (:pr:`2744`). Other changes: - Restore ``marshmallow.base.SchemaABC`` for backwards-compatibility (:issue:`2743`). Note that this class is deprecated and will be removed in marshmallow 4. Use `marshmallow.schema.Schema` as a base class for type-checking instead. 3.24.2 (2025-01-08) ******************* Changes: - Don't override ``__new__`` to avoid breaking usages of `inspect.signature` with `Field ` classes. This allows marshmallow-sqlalchemy users to upgrade marshmallow without upgrading to marshmallow-sqlalchemy>=1.1.1. Documentation: - Add top-level API back to docs (:issue:`2739`). Thanks :user:`llucax` for reporting. 3.24.1 (2025-01-06) ******************* Bug fixes: - Typing: Fix typing for `class_registry.get_class ` (:pr:`2735`). 3.24.0 (2025-01-06) ******************* Features: - Typing: Improve typings in `marshmallow.fields` (:pr:`2723`). - Typing: Replace type comments with inline typings (:pr:`2718`). Bug fixes: - Typing: Fix type hint for ``nested`` parameter of `Nested `. Deprecations: - Custom validators should raise a `ValidationError ` for invalid values. Returning `False`` is no longer supported . - Deprecate ``context`` parameter of `Schema ` (:issue:`1826`). Use `contextVars.ContextVar` to pass context data instead. - `Field `, `Mapping `, and `Number ` should no longer be used as fields within schemas. Use their subclasses instead. 3.23.3 (2025-01-03) ******************* Bug fixes: - Typing: Fix typing for `Schema.from_dict ` (:issue:`1653`). Thanks :user:`SteadBytes` for reporting. Support: - Documentation: Various documentation cleanups, including more concise docs in the `marshmallow.fields` API reference (:issue:`2307`). Thanks :user:`AbdealiLoKo` for reporting. 3.23.2 (2024-12-18) ******************* Bug fixes: - Improve type hint formatting for ``Field``, ``Nested``, and ``Function`` fields to resolve PyCharm warnings (:issue:`2268`). Thanks :user:`Fares-Abubaker` for reporting and fixing. 3.23.1 (2024-11-01) ******************* Support: - Document ``absolute`` parameter of ``URL`` field (:pr:`2327`). - Documentation: Remove (outdated) minimum Python 3 minor version in documentation and README (:pr:`2323`). 3.23.0 (2024-10-17) ******************* Features: - Typing: replace "type" with specific metaclass for ``Schema`` and ``Field``. Other changes: - Officially support Python 3.13 (:pr:`2319`). - Drop support for Python 3.8 (:pr:`2318`). 3.22.0 (2024-08-20) ******************* Features: - Add ``many`` Meta option to ``Schema`` so it expects a collection by default (:issue:`2270`). Thanks :user:`himalczyk` for reporting and :user:`deckar01` for the PR. - Refactor hooks (:pr:`2279`). Thanks :user:`deckar01` for the PR. 3.21.3 (2024-06-05) ******************* Bug fixes: - Fix memory leak that prevented schema instances from getting GC'd (:pr:`2277`). Thanks :user:`mrcljx` for the PR. 3.21.2 (2024-05-01) ******************* Bug fixes: - Allow timestamp 0 in ``fields.DateTime`` (:issue:`2133`). Thanks :user:`flydzen` for reporting. 3.21.1 (2024-03-04) ******************* Bug fixes: - Fix error message when field is declared as a class and not an instance (:issue:`2245`). Thanks :user:`travnick` for reporting. 3.21.0 (2024-02-26) ******************* Bug fixes: - Fix validation of ``URL`` fields to allow missing user field, per NWG RFC 3986 (:issue:`2232`). Thanks :user:`ddennerline3` for reporting and :user:`deckar01` for the PR. Other changes: - *Backwards-incompatible*: ``__version__``, ``__parsed_version__``, and ``__version_info__`` attributes are deprecated (:issue:`2227`). Use feature detection or ``importlib.metadata.version("marshmallow")`` instead. 3.20.2 (2024-01-09) ******************* Bug fixes: - Fix ``Nested`` field type hint for lambda ``Schema`` types (:pr:`2164`). Thanks :user:`somethingnew2-0` for the PR. Other changes: - Officially support Python 3.12 (:pr:`2188`). Thanks :user:`hugovk` for the PR. 3.20.1 (2023-07-20) ******************* Bug fixes: - Fix call to ``get_declared_fields``: pass ``dict_cls`` again (:issue:`2152`). Thanks :user:`Cheaterman` for reporting. 3.20.0 (2023-07-20) ******************* Features: - Add ``absolute`` parameter to ``URL`` validator and ``Url`` field (:pr:`2123`). Thanks :user:`sirosen` for the PR. - Use Abstract Base Classes to define ``FieldABC`` and ``SchemaABC`` (:issue:`1449`). Thanks :user:`aditkumar72` for the PR. - Use `OrderedSet` as default `set_class`. Schemas are now ordered by default. (:issue:`1744`) Bug fixes: - Handle ``OSError`` and ``OverflowError`` in ``utils.from_timestamp`` (:pr:`2102`). Thanks :user:`TheBigRoomXXL` for the PR. - Fix the default inheritance of nested partial schemas (:issue:`2149`). Thanks :user:`matejsp` for reporting. Other changes: - Officially support Python 3.11 (:pr:`2067`). - Drop support for Python 3.7 (:pr:`2135`). 3.19.0 (2022-11-11) ******************* Features: - Add ``timestamp`` and ``timestamp_ms`` formats to ``fields.DateTime`` (:issue:`612`). Thanks :user:`vgavro` for the suggestion and thanks :user:`vanHoi` for the PR. 3.18.0 (2022-09-15) ******************* Features: - Add ``Enum`` field (:pr:`2017`) and (:pr:`2044`). Bug fixes: - Fix typing in ``Field._serialize`` signature (:pr:`2046`). 3.17.1 (2022-08-22) ******************* Bug fixes: - Add return type to ``fields.Email.__init__`` (:pr:`2018`). Thanks :user:`kkirsche` for the PR. - Add missing type hint to IPInterface __init__ (:pr:`2036`). 3.17.0 (2022-06-26) ******************* Features: - Support serialization as float in ``TimeDelta`` field (:pr:`1998`). Thanks :user:`marcosatti` for the PR. - Add ``messages_dict`` property to ``ValidationError`` to facilitate type checking (:pr:`1976`). Thanks :user:`sirosen` for the PR. 3.16.0 (2022-05-29) ******************* Features: - Raise ``ValueError`` if an invalid value is passed to the ``unknown`` argument (:issue:`1721`, :issue:`1732`). Thanks :user:`sirosen` for the PR. Other changes: - Set lower bound for ``packaging`` requirement (:issue:`1957`). Thanks :user:`MatthewNicolTR` for reporting and thanks :user:`sirosen` for the PR. - Improve warning messages by passing ``stacklevel`` (:pr:`1986`). Thanks :user:`tirkarthi` for the PR. 3.15.0 (2022-03-12) ******************* Features: - Allow passing a ``dict`` to ``fields.Nested`` (:pr:`1935`). Thanks :user:`sirosen` for the PR. Other changes: - Address distutils deprecation warning in Python 3.10 (:pr:`1903`). Thanks :user:`kkirsche` for the PR. - Add py310 to black target-version (:pr:`1921`). - Drop support for Python 3.6 (:pr:`1923`). - Use postponed evaluation of annotations (:pr:`1932`). Thanks :user:`Isira-Seneviratne` for the PR. 3.14.1 (2021-11-13) ******************* Bug fixes: - Fix publishing type hints per `PEP-561 `_ (:pr:`1905`). Thanks :user:`bwindsor` for the catch and patch. 3.14.0 (2021-10-17) ******************* Bug fixes: - Fix ``fields.TimeDelta`` serialization precision (:issue:`1865`). Thanks :user:`yarsanich` for reporting. Other changes: - Fix type-hints for ``data`` arg in ``Schema.validate`` to accept list of dictionaries (:issue:`1790`, :pr:`1868`). Thanks :user:`yourun-proger` for PR. - Improve warning when passing metadata as keyword arguments (:pr:`1882`). Thanks :user:`traherom` for the PR. - Don't build universal wheels. We don't support Python 2 anymore. (:issue:`1860`) Thanks :user:`YKdvd` for reporting. - Make the build reproducible (:pr:`1862`). - Drop support for Python 3.5 (:pr:`1863`). - Test against Python 3.10 (:pr:`1888`). 3.13.0 (2021-07-21) ******************* Features: - Replace ``missing``/``default`` field parameters with ``load_default``/``dump_default`` (:pr:`1742`). Thanks :user:`sirosen` for the PR. Deprecations: - The use of ``missing``/``default`` field parameters is deprecated and will be removed in marshmallow 4. ``load_default``/``dump_default`` should be used instead. 3.12.2 (2021-07-06) ******************* Bug fixes: - Don't expose ``Field``\s as ``Schema`` attributes. This reverts a change introduced in 3.12.0 that causes issues when field names conflict with ``Schema`` attributes or methods. ``Fields``\s are still accessible on a ``Schema`` instance through the ``fields`` attribute. (:pr:`1843`) 3.12.1 (2021-05-10) ******************* Bug fixes: - Fix bug that raised an ``AttributeError`` when instantiating a ``Schema`` with a field named ``parent`` (:issue:`1808`). Thanks :user:`flying-sheep` for reporting and helping with the fix. 3.12.0 (2021-05-09) ******************* Features: - Add ``validate.And`` (:issue:`1768`). Thanks :user:`rugleb` for the suggestion. - Add type annotations to ``marshmallow.decorators`` (:issue:`1788`, :pr:`1789`). Thanks :user:`michaeldimchuk` for the PR. - Let ``Field``\s be accessed by name as ``Schema`` attributes (:pr:`1631`). Other changes: - Improve types in ``marshmallow.validate`` (:pr:`1786`). - Make ``marshmallow.validate.Validator`` an abstract base class (:pr:`1786`). - Remove unnecessary list cast (:pr:`1785`). 3.11.1 (2021-03-29) ******************* Bug fixes: - Fix treatment of dotted keys when ``unknown=INCLUDE`` (:issue:`1506`). Thanks :user:`rbu` for reporting and thanks :user:`sirosen` for the fix (:pr:`1745`). 3.11.0 (2021-03-28) ******************* Features: - Add ``fields.IPInterface``, ``fields.IPv4Interface``, and ``IPv6Interface`` (:issue:`1733`). Thanks :user:`madeinoz67` for the suggestion and the PR. - Raise ``AttributeError`` for missing methods when using ``fields.Method`` (:pr:`1675`). Thanks :user:`lassandroan`. Other changes: - Remove unnecessary ``hasattr`` and ``getattr`` checks in ``Field`` (:pr:`1770`). 3.10.0 (2020-12-19) ******************* Deprecations: - Passing field metadata via keyword arguments is deprecated and will be removed in marshmallow 4 (:issue:`1350`). Use the explicit ``metadata=...`` argument instead. Thanks :user:`sirosen`. 3.9.1 (2020-11-07) ****************** Bug fixes: - Cast to mapping type in ``Mapping.serialize`` and ``Mapping.deserialize`` (:pr:`1685`). - Fix bug letting ``Dict`` pass invalid dict on deserialization when no key or value ``Field`` is specified (:pr:`1685`). 3.9.0 (2020-10-31) ****************** Features: - Add ``format`` argument to ``fields.Time`` and ``timeformat`` ``class Meta`` option (:issue:`686`). Thanks :user:`BennyAlex` for the suggestion and thanks :user:`infinityxxx` for the PR. Other changes: - Remove usage of implicit ``typing.Optional`` (:issue:`1663`). Thanks :user:`nadega` for the PR. 3.8.0 (2020-09-16) ****************** Features: - Add ``fields.IP``, ``fields.IPv4`` and ``fields.IPv6`` (:pr:`1485`). Thanks :user:`mgetka` for the PR. Bug fixes: - Fix typing in ``AwareDateTime`` (:pr:`1658`). Thanks :user:`adithyabsk` for reporting. 3.7.1 (2020-07-20) ****************** Bug fixes: - ``fields.Boolean`` correctly serializes non-hashable types (:pr:`1633`). Thanks :user:`jun0jang` for the PR. 3.7.0 (2020-07-08) ****************** Deprecations: - ``marshmallow.pprint`` is deprecated and will be removed in marshmallow 4 (:issue:`1588`). Support: - Document ``default_error_messages`` on field classes (:pr:`1619`). Thanks :user:`weeix`. Bug fixes: - Fix passing ``only`` and ``exclude`` to ``Nested`` with an ordered ``Schema`` (:pr:`1627`). Thanks :user:`juannorris` for the PR. 3.6.1 (2020-06-02) ****************** No code changes--only docs and contributor-facing updates in this release. Support: - Documentation: improve custom fields example (:issue:`1538`). Thanks :user:`pablospizzamiglio` for reporting the problem with the old example and thanks :user:`Resinderate` for the PR. - Documentation: Split up API reference into multiple pages and add summary tables (:pr:`1587`). Thanks :user:`EpicWink` for the PR. 3.6.0 (2020-05-08) ****************** Features: - Add ``validate.ContainsNoneOf`` (:issue:`1528`). Thanks :user:`Resinderate` for the suggestion and the PR. 3.5.2 (2020-04-30) ****************** Bug fixes: - Fix typing in ``class_registry`` (:pr:`1574`). Thanks :user:`mahenzon`. 3.5.1 (2020-03-05) ****************** Bug fixes: - Includes bug fix from 2.21.0. 3.5.0 (2020-02-19) ****************** Bug fixes: - Fix list of nullable nested fields ``List(Nested(Field, allow_none=True)`` (:issue:`1497`). Because this fix reverts an optimization introduced to speed-up serialization and deserialization of lists of nested fields, a negative impact on performance in this specific case is expected. 3.4.0 (2020-02-02) ****************** Features: - Improve type coverage (:issue:`1479`). Thanks :user:`Reskov`. Bug fixes: - Fix typing for ``data`` param of ``Schema.load`` and ``ValidationError`` (:issue:`1492`). Thanks :user:`mehdigmira` for reporting and thanks :user:`dfirst` for the PR. Other changes: - Remove unnecessary typecasts (:pr:`1500`). Thanks :user:`hukkinj1`. - Remove useless ``_serialize`` override in ``UUID`` field (:pr:`1489`). 3.3.0 (2019-12-05) ****************** Features: - ``fields.Nested`` may take a callable that returns a schema instance. Use this to resolve order-of-declaration issues when schemas nest each other (:issue:`1146`). .. code-block:: python # <3.3 class AlbumSchema(Schema): title = fields.Str() artist = fields.Nested("ArtistSchema", only=("name",)) class ArtistSchema(Schema): name = fields.Str() albums = fields.List(fields.Nested(AlbumSchema)) # >=3.3 class AlbumSchema(Schema): title = fields.Str() artist = fields.Nested(lambda: ArtistSchema(only=("name",))) class ArtistSchema(Schema): name = fields.Str() albums = fields.List(fields.Nested(AlbumSchema)) Deprecations: - Passing the string ``"self"`` to ``fields.Nested`` is deprecated. Use a callable instead. .. code-block:: python from marshmallow import Schema, fields # <3.3 class PersonSchema(Schema): partner = fields.Nested("self", exclude=("partner",)) friends = fields.List(fields.Nested("self")) # >=3.3 class PersonSchema(Schema): partner = fields.Nested(lambda: PersonSchema(exclude=("partner"))) friends = fields.List(fields.Nested(lambda: PersonSchema())) Other changes: - Fix typing for ``Number._format_num`` (:pr:`1466`). Thanks :user:`hukkinj1`. - Make mypy stricter and remove dead code (:pr:`1467`). Thanks again, :user:`hukkinj1`. 3.2.2 (2019-11-04) ****************** Bug fixes: - Don't load fields for which ``load_only`` and ``dump_only`` are both ``True`` (:pr:`1448`). - Fix types in ``marshmallow.validate`` (:pr:`1446`). Support: - Test against Python 3.8 (:pr:`1431`). 3.2.1 (2019-09-30) ****************** Bug fixes: - Fix typing for ``Schema.dump[s]`` (:pr:`1416`). 3.2.0 (2019-09-17) ****************** Features: - Add type annotations to ``marshmallow.schema`` and ``marshmallow.validate`` (:pr:`1407`, :issue:`663`). Bug fixes: - Fix compatibility with Python < 3.5.3 (:issue:`1409`). Thanks :user:`lukaszdudek-silvair` for reporting. Refactoring: - Remove unnecessary ``BaseSchema`` superclass (:pr:`1406`). 3.1.1 (2019-09-16) ****************** Bug fixes: - Restore inheritance hierarchy of ``Number`` fields (:pr:`1403`). ``fields.Integer`` and ``fields.Decimal`` inherit from ``fields.Number``. - Fix bug that raised an uncaught error when a nested schema instance had an unpickleable object in its context (:issue:`1404`). Thanks :user:`metheoryt` for reporting. 3.1.0 (2019-09-15) ****************** Features: - Add more type annotations (:issue:`663`). Type information is distributed per `PEP 561 `_ . Thanks :user:`fuhrysteve` for helping with this. Bug fixes: - Includes bug fix from 2.20.5. 3.0.5 (2019-09-12) ****************** Bug fixes: - Fix bug that raised an uncaught error when passing both a schema instance and ``only`` to ``Nested`` (:pr:`1395`). This bug also affected passing a schema instance to ``fields.Pluck``. 3.0.4 (2019-09-11) ****************** Bug fixes: - Fix propagating dot-delimited ``only`` and ``exclude`` parameters to nested schema instances (:issue:`1384`). - Includes bug fix from 2.20.4 (:issue:`1160`). 3.0.3 (2019-09-04) ****************** Bug fixes: - Handle when ``data_key`` is an empty string (:issue:`1378`). Thanks :user:`jtrakk` for reporting. 3.0.2 (2019-09-04) ****************** Bug fixes: - Includes bug fix from 2.20.3 (:pr:`1376`). - Fix incorrect ``super()`` call in ``SchemaMeta.__init__`` (:pr:`1362`). 3.0.1 (2019-08-21) ****************** Bug fixes: - Fix bug when nesting ``fields.DateTime`` within ``fields.List`` or ``fields.Tuple`` (:issue:`1357`). This bug was introduced in 3.0.0rc9. Thanks :user:`zblz` for reporting. 3.0.0 (2019-08-18) ****************** Features: - Optimize ``List(Nested(...))`` (:issue:`779`). - Minor performance improvements and cleanup (:pr:`1328`). - Add ``Schema.from_dict`` (:issue:`1312`). Deprecations/Removals: - ``Field.fail`` is deprecated. Use ``Field.make_error`` instead. - Remove UUID validation from ``fields.UUID``, for consistency with other fields (:issue:`1132`). Support: - Various docs improvements (:pr:`1329`). 3.0.0rc9 (2019-07-31) ********************* Features: - *Backwards-incompatible*: Validation does not occur on serialization (:issue:`1132`). This significantly improves serialization performance. - *Backwards-incompatible*: ``DateTime`` does not affect timezone information on serialization and deserialization (:issue:`1234`, :pr:`1278`). - Add ``NaiveDateTime`` and ``AwareDateTime`` to enforce timezone awareness (:issue:`1234`, :pr:`1287`). - *Backwards-incompatible*: ``List`` does not wrap single values in a list on serialization (:pr:`1307`). - *Backwards-incompatible*: ``Schema.handle_error`` receives ``many`` and ``partial`` as keyword arguments (:pr:`1321`). - Use ``raise from`` more uniformly to improve stack traces (:pr:`1313`). - Rename ``Nested.__schema`` to ``Nested._schema`` to prevent name mangling (:issue:`1289`). - Performance improvements (:pr:`1309`). Deprecations/Removals: - ``LocalDateTime`` is removed (:issue:`1234`). - ``marshmallow.utils.utc`` is removed. Use ``datetime.timezone.utc`` instead. Bug fixes: - Fix behavior of ``List(Nested("self"))`` (`#779 (comment) `_). Support: - Document usage of ``validate.Regexp``'s usage ``re.search`` (:issue:`1285`). Thanks :user:`macdonaldezra`. 3.0.0rc8 (2019-07-04) ********************* Features: - Propagate ``only`` and ``exclude`` parameters to ``Nested`` fields within ``List`` and ``Dict`` (:issue:`779`, :issue:`946`). - Use ``email.utils.parsedate_to_datetime`` instead of conditionally using dateutil for parsing RFC dates (:pr:`1246`). - Use internal util functions instead of conditionally using dateutil for parsing ISO 8601 datetimes, dates and times. Timezone info is now correctly deserialized whether or not dateutil is installed. (:pr:`1265`) - Improve error messages for ``validate.Range``. - Use ``raise from error`` for better stack traces (:pr:`1254`). Thanks :user:`fuhrysteve`. - python-dateutil is no longer used. This resolves the inconsistent behavior based on the presence of python-dateutil (:issue:`497`, :issue:`1234`). Bug fixes: - Fix method resolution for ``__init__`` method of ``fields.Email`` and ``fields.URL`` (:issue:`1268`). Thanks :user:`dursk` for the catch and patch. - Includes bug fixes from 2.19.4 and 2.19.5. Other changes: - *Backwards-incompatible*: Rename ``fields.List.container`` to ``fields.List.inner``, ``fields.Dict.key_container`` to ``fields.Dict.key_field``, and ``fields.Dict.value_container`` to ``fields.Dict.value_field``. - Switch to Azure Pipelines for CI (:issue:`1261`). 3.0.0rc7 (2019-06-15) ********************* Features: - *Backwards-incompatible*: ``many`` is passed as a keyword argument to methods decorated with ``pre_load``, ``post_load``, ``pre_dump``, ``post_dump``, and ``validates_schema``. ``partial`` is passed as a keyword argument to methods decorated with ``pre_load``, ``post_load`` and ``validates_schema``. ``**kwargs`` should be added to all decorated methods. - Add ``min_inclusive`` and ``max_exclusive`` parameters to ``validate.Range`` (:issue:`1221`). Thanks :user:`kdop` for the PR. Bug fixes: - Fix propagation of ``partial`` to ``Nested`` containers (part of :issue:`779`). - Includes bug fix from 2.19.3. Other changes: - *Backwards-incompatible*: Use keyword-only arguments (:issue:`1216`). 3.0.0rc6 (2019-05-05) ********************* Support: - *Backwards-incompatible*: Remove support for Python 2 (:issue:`1120`). Only Python>=3.5 is supported. Thank you :user:`rooterkyberian` for the suggestion and the PR. - *Backwards-incompatible*: Remove special-casing in ``fields.List`` and ``fields.Tuple`` for accessing nested attributes (:pr:`1188`). Use ``fields.List(fields.Pluck(...))`` instead. - Add ``python_requires`` to ``setup.py`` (:pr:`1194`). Thanks :user:`hugovk`. - Upgrade syntax with ``pyupgrade`` in pre-commit (:pr:`1195`). Thanks again :user:`hugovk`. 3.0.0rc5 (2019-03-30) ********************* Features: - Allow input value to be included in error messages for a number of fields (:pr:`1129`). Thanks :user:`hdoupe` for the PR. - Improve default error messages for ``OneOf`` and ``ContainsOnly`` (:issue:`885`). Thanks :user:`mcgfeller` for the suggestion and :user:`maxalbert` for the PR. Deprecations/Removals: - Remove ``fields.FormattedString`` (:issue:`1141`). Use ``fields.Function`` or ``fields.Method`` instead. Bug fixes: - Includes bug fix from 2.19.2. 3.0.0rc4 (2019-02-08) ********************* Features: - Add ``fields.Tuple`` (:issue:`1103`) Thanks :user:`zblz` for the PR. - Add ``fields.Mapping``, which makes it easier to support other mapping types (e.g. ``OrderedDict``) (:issue:`1092`). Thank :user:`sayanarijit` for the suggestion and the PR. 3.0.0rc3 (2019-01-13) ********************* Features: - Make the error messages for "unknown fields" and "invalid data type" configurable (:issue:`852`). Thanks :user:`Dunstrom` for the PR. - ``fields.Boolean`` parses ``"yes"``/``"no"`` values (:pr:`1081`). Thanks :user:`r1b`. Other changes: - *Backwards-incompatible with previous 3.x versions*: Change ordering of ``keys`` and ``values`` arguments to ``fields.Dict``. - Remove unused code in ``marshmallow.utils``: ``is_indexable_but_not_string``, ``float_to_decimal``, ``decimal_to_fixed``, ``from_iso`` (:pr:`1088`). - Remove unused ``marshmallow.compat.string_types``. Bug fixes: - Includes bug fix from 2.18.0. 3.0.0rc2 (2019-01-03) ********************* Features: - Add ``register`` *class Meta* option to allow bypassing marshmallow's internal class registry when memory usage is critical (:issue:`660`). Bug fixes: - Fix serializing dict-like objects with properties (:issue:`1060`). Thanks :user:`taion` for the fix. - Fix populating ``ValidationError.valid_data`` for ``List`` and ``Dict`` fields (:issue:`766`). Other changes: - Add ``marshmallow.__version_info__`` (:pr:`1074`). - Remove the ``marshmallow.marshalling`` internal module (:pr:`1070`). - A ``ValueError`` is raised when the ``missing`` parameter is passed for required fields (:issue:`1040`). - Extra keyword arguments passed to ``ValidationError`` in validators are no longer passed to the final ``ValidationError`` raised upon validation completion (:issue:`996`). 3.0.0rc1 (2018-11-29) ********************* Features: - *Backwards-incompatible*: Rework ``ValidationError`` API. It now expects a single field name, and error structures are merged in the final ``ValidationError`` raised when validation completes. This allows schema-level validators to raise errors for individual fields (:issue:`441`). Thanks :user:`maximkulkin` for writing the original ``merge_errors`` implementation in :pr:`442` and thanks :user:`lafrech` for completing the implementation in :pr:`1026`. Bug fixes: - Fix ``TypeError`` when serializing ``None`` with ``Pluck`` (:pr:`1049`). Thanks :user:`toffan` for the catch and patch. 3.0.0b20 (2018-11-01) ********************* Bug fixes: - Includes bug fixes from 2.16.2 and 2.16.3. 3.0.0b19 (2018-10-24) ********************* Features: - Support partial loading of nested fields (:pr:`438`). Thanks :user:`arbor-dwatson` for the PR. *Note*: Subclasses of ``fields.Nested`` now take an additional ``partial`` parameter in the ``_deserialize`` method. Bug fixes: - Restore ``Schema.TYPE_MAPPING``, which was removed in 3.0.0b17 (:issue:`1012`). Other changes: - *Backwards-incompatible*: ``_serialize`` and ``_deserialize`` methods of all ``fields.Field`` subclasses must accept ``**kwargs`` (:pr:`1007`). 3.0.0b18 (2018-10-15) ********************* Bug fixes: - Fix ``Date`` deserialization when using custom format (:issue:`1001`). Thanks :user:`Ondkloss` for reporting. Deprecations/Removals: - ``prefix`` parameter or ``Schema`` class is removed (:issue:`991`). The same can be achieved using a ``@post_dump`` method. 3.0.0b17 (2018-10-13) ********************* Features: - Add ``format`` option to ``Date`` field (:pr:`869`). - *Backwards-incompatible*: Rename ``DateTime``'s ``dateformat`` Meta option to ``datetimeformat``. ``dateformat`` now applies to ``Date`` (:pr:`869`). Thanks :user:`knagra` for implementing these changes. - Enforce ISO 8601 when deserializing date and time (:issue:`899`). Thanks :user:`dushr` for the report and the work on the PR. - *Backwards-incompatible*: Raise ``ValueError`` on ``Schema`` instantiation in case of ``attribute`` or ``data_key`` collision (:pr:`992`). Bug fixes: - Fix inconsistencies in field inference by refactoring the inference feature into a dedicated field (:issue:`809`). Thanks :user:`taion` for the PR. - When ``unknown`` is not passed to ``Nested``, default to nested ``Schema`` ``unknown`` meta option rather than ``RAISE`` (:pr:`963`). Thanks :user:`vgavro` for the PR. - Fix loading behavior of ``fields.Pluck`` (:pr:`990`). - Includes bug fix from 2.16.0. 3.0.0b16 (2018-09-20) ********************* Bug fixes: - Fix ``root`` attribute for nested container fields on inheriting schemas (:issue:`956`). Thanks :user:`bmcbu` for reporting. 3.0.0b15 (2018-09-18) ********************* Bug fixes: - Raise ``ValidationError`` instead of ``TypeError`` when non-iterable types are validated with ``many=True`` (:issue:`851`). - ``many=True`` no longer iterates over ``str`` and ``collections.abc.Mapping`` objects and instead raises a ``ValidationError`` with ``{'_schema': ['Invalid input type.']}`` (:issue:`930`). - Return ``[]`` as ``ValidationError.valid_data`` instead of ``{}`` when ``many=True`` (:issue:`907`). Thanks :user:`tuukkamustonen` for implementing these changes. 3.0.0b14 (2018-09-15) ********************* Features: - Add ``fields.Pluck`` for serializing a single field from a nested object (:issue:`800`). Thanks :user:`timc13` for the feedback and :user:`deckar01` for the implementation. - *Backwards-incompatible*: Passing a string argument as ``only`` to ``fields.Nested`` is no longer supported. Use ``fields.Pluck`` instead (:issue:`800`). - Raise a ``StringNotCollectionError`` if ``only`` or ``exclude`` is passed as a string to ``fields.Nested`` (:pr:`931`). - *Backwards-incompatible*: ``Float`` takes an ``allow_nan`` parameter to explicitly allow serializing and deserializing special values (``nan``, ``inf`` and ``-inf``). ``allow_nan`` defaults to ``False``. Other changes: - *Backwards-incompatible*: ``Nested`` field now defaults to ``unknown=RAISE`` instead of ``EXCLUDE``. This harmonizes behavior with ``Schema`` that already defaults to ``RAISE`` (:issue:`908`). Thanks :user:`tuukkamustonen`. - Tested against Python 3.7. 3.0.0b13 (2018-08-04) ********************* Bug fixes: - Errors reported by a schema-level validator for a field in a ``Nested`` field are stored under corresponding field name, not ``_schema`` key (:pr:`862`). - Includes bug fix from 2.15.4. Other changes: - *Backwards-incompatible*: The ``unknown`` option now defaults to ``RAISE`` (`#524 (comment) `_, :issue:`851`). - *Backwards-incompatible*: When a schema error is raised with a ``dict`` as payload, the ``dict`` overwrites any existing error list. Before this change, it would be appended to the list. - Raise a `StringNotCollectionError` if ``only`` or ``exclude`` is passed as a string (:issue:`316`). Thanks :user:`paulocheque` for reporting. 3.0.0b12 (2018-07-04) ********************* Features: - The behavior to apply when encountering unknown fields while deserializing can be controlled with the ``unknown`` option (:issue:`524`, :issue:`747`, :issue:`127`). It makes it possible to either "include", "exclude", or "raise". Thanks :user:`tuukkamustonen` for the suggestion and thanks :user:`ramnes` for the PR. .. warning:: The default for ``unknown`` will be changed to ``RAISE`` in the next release. Other changes: - *Backwards-incompatible*: Pre/Post-processors MUST return modified data. Returning ``None`` does not imply data were mutated (:issue:`347`). Thanks :user:`tdevelioglu` for reporting. - *Backwards-incompatible*: ``only`` and ``exclude`` are bound by declared and additional fields. A ``ValueError`` is raised if invalid fields are passed (:issue:`636`). Thanks :user:`jan-23` for reporting. Thanks :user:`ikilledthecat` and :user:`deckar01` for the PRs. - Format code using pre-commit (:pr:`855`). Deprecations/Removals: - ``ValidationError.fields`` is removed (:issue:`840`). Access field instances from ``Schema.fields``. 3.0.0b11 (2018-05-20) ********************* Features: - Clean up code for schema hooks (:pr:`814`). Thanks :user:`taion`. - Minor performance improvement from simplifying ``utils.get_value`` (:pr:`811`). Thanks again :user:`taion`. - Add ``require_tld`` argument to ``fields.URL`` (:issue:`749`). Thanks :user:`DenerKup` for reporting and thanks :user:`surik00` for the PR. - ``fields.UUID`` deserializes ``bytes`` strings using ``UUID(bytes=b'...')`` (:pr:`625`). Thanks :user:`JeffBerger` for the suggestion and the PR. Bug fixes: - Fields nested within ``Dict`` correctly inherit context from their parent schema (:issue:`820`). Thanks :user:`RosanneZe` for reporting and :user:`deckar01` for the PR. - Includes bug fix from 2.15.3. 3.0.0b10 (2018-05-10) ********************* Bug fixes: - Includes bugfixes from 2.15.2. 3.0.0b9 (2018-04-25) ******************** Features: - *Backwards-incompatible*: ``missing`` and ``default`` values are passed in deserialized form (:issue:`378`). Thanks :user:`chadrik` for the suggestion and thanks :user:`lafrech` for the PR. Bug fixes: - Includes the bugfix from 2.15.1. 3.0.0b8 (2018-03-24) ******************** Features: - *Backwards-incompatible*: Add ``data_key`` parameter to fields for specifying the key in the input and output data dict. This parameter replaces both ``load_from`` and ``dump_to`` (:issue:`717`). Thanks :user:`lafrech`. - *Backwards-incompatible*: When ``pass_original=True`` is passed to one of the decorators and a collection is being (de)serialized, the ``original_data`` argument will be a single object unless ``pass_many=True`` is also passed to the decorator (:issue:`315`, :issue:`743`). Thanks :user:`stj` for the PR. - *Backwards-incompatible*: Don't recursively check nested required fields when the ``Nested`` field's key is missing (:issue:`319`). This reverts :pr:`235`. Thanks :user:`chekunkov` reporting and thanks :user:`lafrech` for the PR. - *Backwards-incompatible*: Change error message collection for ``Dict`` field (:issue:`730`). Note: this is backwards-incompatible with previous 3.0.0bX versions. Thanks :user:`shabble` for the report and thanks :user:`lafrech` for the PR. 3.0.0b7 (2018-02-03) ******************** Features: - *Backwards-incompatible*: Schemas are always strict (:issue:`377`). The ``strict`` parameter is removed. - *Backwards-incompatible*: ``Schema().load`` and ``Schema().dump`` return ``data`` instead of a ``(data, errors)`` tuple (:issue:`598`). - *Backwards-incompatible*: ``Schema().load(None)`` raises a ``ValidationError`` (:issue:`511`). See :ref:`upgrading_3_0` for a guide on updating your code. Thanks :user:`lafrech` for implementing these changes. Special thanks to :user:`MichalKononenko`, :user:`douglas-treadwell`, and :user:`maximkulkin` for the discussions on these changes. Other changes: - *Backwards-incompatible*: Field name is not checked when ``load_from`` is specified (:pr:`714`). Thanks :user:`lafrech`. Support: - Add `Code of Conduct `_. 3.0.0b6 (2018-01-02) ******************** Bug fixes: - Fixes ``ValidationError.valid_data`` when a nested field contains errors (:issue:`710`). This bug was introduced in 3.0.0b3. Thanks :user:`lafrech`. Other changes: - *Backwards-incompatible*: ``Email`` and ``URL`` fields don't validate on serialization (:issue:`608`). This makes them more consistent with the other fields and improves serialization performance. Thanks again :user:`lafrech`. - ``validate.URL`` requires square brackets around IPv6 URLs (:issue:`707`). Thanks :user:`harlov`. 3.0.0b5 (2017-12-30) ******************** Features: - Add support for structured dictionaries by providing values and keys arguments to the ``Dict`` field's constructor. This mirrors the ``List`` field's ability to validate its items (:issue:`483`). Thanks :user:`deckar01`. Other changes: - *Backwards-incompatible*: ``utils.from_iso`` is deprecated in favor of ``utils.from_iso_datetime`` (:issue:`694`). Thanks :user:`sklarsa`. 3.0.0b4 (2017-10-23) ******************** Features: - Add support for millisecond, minute, hour, and week precisions to ``fields.TimeDelta`` (:issue:`537`). Thanks :user:`Fedalto` for the suggestion and the PR. - Includes features from release 2.14.0. Support: - Copyright year in docs uses ``CHANGELOG.rst``'s modified date for reproducible builds (:issue:`679`). Thanks :user:`bmwiedemann`. - Test against Python 3.6 in tox. Thanks :user:`Fedalto`. - Fix typo in exception message (:issue:`659`). Thanks :user:`wonderbeyond` for reporting and thanks :user:`yoichi` for the PR. 3.0.0b3 (2017-08-20) ******************** Features: - Add ``valid_data`` attribute to ``ValidationError``. - Add ``strict`` parameter to ``Integer`` (:issue:`667`). Thanks :user:`yoichi`. Deprecations/Removals: - Deprecate ``json_module`` option in favor of ``render_module`` (:issue:`364`, :issue:`130`). Thanks :user:`justanr` for the suggestion. Bug fixes: - Includes bug fixes from releases 2.13.5 and 2.13.6. - *Backwards-incompatible*: ``Number`` fields don't accept booleans as valid input (:issue:`623`). Thanks :user:`tuukkamustonen` for the suggestion and thanks :user:`rowillia` for the PR. Support: - Add benchmark script. Thanks :user:`rowillia`. 3.0.0b2 (2017-03-19) ******************** Features: - Add ``truthy`` and ``falsy`` params to ``fields.Boolean`` (:issue:`580`). Thanks :user:`zwack` for the PR. Note: This is potentially a breaking change if your code passes the `default` parameter positionally. Pass `default` as a keyword argument instead, e.g. ``fields.Boolean(default=True)``. Other changes: - *Backwards-incompatible*: ``validate.ContainsOnly`` allows empty and duplicate values (:issue:`516`, :issue:`603`). Thanks :user:`maximkulkin` for the suggestion and thanks :user:`lafrech` for the PR. Bug fixes: - Includes bug fixes from release 2.13.4. 3.0.0b1 (2017-03-10) ******************** Features: - ``fields.Nested`` respects ``only='field'`` when deserializing (:issue:`307`). Thanks :user:`erlingbo` for the suggestion and the PR. - ``fields.Boolean`` parses ``"on"``/``"off"`` (:issue:`580`). Thanks :user:`marcellarius` for the suggestion. Other changes: - Includes changes from release 2.13.2. - *Backwards-incompatible*: ``skip_on_field_errors`` defaults to ``True`` for ``validates_schema`` (:issue:`352`). 3.0.0a1 (2017-02-26) ******************** Features: - ``dump_only`` and ``load_only`` for ``Function`` and ``Method`` are set based on ``serialize`` and ``deserialize`` arguments (:issue:`328`). Other changes: - *Backwards-incompatible*: ``fields.Method`` and ``fields.Function`` no longer swallow ``AttributeErrors`` (:issue:`395`). Thanks :user:`bereal` for the suggestion. - *Backwards-incompatible*: ``validators.Length`` is no longer a subclass of ``validators.Range`` (:issue:`458`). Thanks :user:`deckar01` for the catch and patch. - *Backwards-incompatible*: ``utils.get_func_args`` no longer returns bound arguments. This is consistent with the behavior of ``inspect.signature``. This change prevents a DeprecationWarning on Python 3.5 (:issue:`415`, :issue:`479`). Thanks :user:`deckar01` for the PR. - *Backwards-incompatible*: Change the signature of ``utils.get_value`` and ``Schema.get_attribute`` for consistency with Python builtins (e.g. ``getattr``) (:issue:`341`). Thanks :user:`stas` for reporting and thanks :user:`deckar01` for the PR. - *Backwards-incompatible*: Don't unconditionally call callable attributes (:issue:`430`, reverts :issue:`242`). Thanks :user:`mirko` for the suggestion. - Drop support for Python 2.6 and 3.3. Deprecation/Removals: - Remove ``__error_handler__``, ``__accessor__``, ``@Schema.error_handler``, and ``@Schema.accessor``. Override ``Schema.handle_error`` and ``Schema.get_attribute`` instead. - Remove ``func`` parameter of ``fields.Function``. Remove ``method_name`` parameter of ``fields.Method`` (issue:`325`). Use the ``serialize`` parameter instead. - Remove ``extra`` parameter from ``Schema``. Use a ``@post_dump`` method to add additional data. 2.21.0 (2020-03-05) ******************* Bug fixes: - Don't match string-ending newlines in ``URL`` and ``Email`` fields (:issue:`1522`). Thanks :user:`nbanmp` for the PR. Other changes: - Drop support for Python 3.4 (:pr:`1525`). 2.20.5 (2019-09-15) ******************* Bug fixes: - Fix behavior when a non-list collection is passed to the ``validate`` argument of ``fields.Email`` and ``fields.URL`` (:issue:`1400`). 2.20.4 (2019-09-11) ******************* Bug fixes: - Respect the ``many`` value on ``Schema`` instances passed to ``Nested`` (:issue:`1160`). Thanks :user:`Kamforka` for reporting. 2.20.3 (2019-09-04) ******************* Bug fixes: - Don't swallow ``TypeError`` exceptions raised by ``Field._bind_to_schema`` or ``Schema.on_bind_field`` (:pr:`1376`). 2.20.2 (2019-08-20) ******************* Bug fixes: - Prevent warning about importing from ``collections`` on Python 3.7 (:pr:`1354`). Thanks :user:`nicktimko` for the PR. 2.20.1 (2019-08-13) ******************* Bug fixes: - Fix bug that raised ``TypeError`` when invalid data type is passed to a nested schema with ``@validates`` (:issue:`1342`). 2.20.0 (2019-08-10) ******************* Bug fixes: - Fix deprecated functions' compatibility with Python 2 (:issue:`1337`). Thanks :user:`airstandley` for the catch and patch. - Fix error message consistency for invalid input types on nested fields (:issue:`1303`). This is a backport of the fix in :pr:`857`. Thanks :user:`cristi23` for the thorough bug report and the PR. Deprecation/Removals: - Python 2.6 is no longer officially supported (:issue:`1274`). 2.19.5 (2019-06-18) ******************* Bug fixes: - Fix deserializing ISO8601-formatted datetimes with less than 6-digit miroseconds (:issue:`1251`). Thanks :user:`diego-plan9` for reporting. 2.19.4 (2019-06-16) ******************* Bug fixes: - Microseconds no longer gets lost when deserializing datetimes without dateutil installed (:issue:`1147`). 2.19.3 (2019-06-15) ******************* Bug fixes: - Fix bug where nested fields in ``Meta.exclude`` would not work on multiple instantiations (:issue:`1212`). Thanks :user:`MHannila` for reporting. 2.19.2 (2019-03-30) ******************* Bug fixes: - Handle ``OverflowError`` when (de)serializing large integers with ``fields.Float`` (:pr:`1177`). Thanks :user:`brycedrennan` for the PR. 2.19.1 (2019-03-16) ******************* Bug fixes: - Fix bug where ``Nested(many=True)`` would skip first element when serializing a generator (:issue:`1163`). Thanks :user:`khvn26` for the catch and patch. 2.19.0 (2019-03-07) ******************* Deprecation/Removal: - A ``RemovedInMarshmallow3`` warning is raised when using ``fields.FormattedString``. Use ``fields.Method`` or ``fields.Function`` instead (:issue:`1141`). 2.18.1 (2019-02-15) ******************* Bug fixes: - A ``ChangedInMarshmallow3Warning`` is no longer raised when ``strict=False`` (:issue:`1108`). Thanks :user:`Aegdesil` for reporting. 2.18.0 (2019-01-13) ******************* Features: - Add warnings for functions in ``marshmallow.utils`` that are removed in marshmallow 3. Bug fixes: - Copying ``missing`` with ``copy.copy`` or ``copy.deepcopy`` will not duplicate it (:pr:`1099`). 2.17.0 (2018-12-26) ******************* Features: - Add ``marshmallow.__version_info__`` (:pr:`1074`). - Add warnings for API that is deprecated or changed to help users prepare for marshmallow 3 (:pr:`1075`). 2.16.3 (2018-11-01) ******************* Bug fixes: - Prevent memory leak when dynamically creating classes with ``type()`` (:issue:`732`). Thanks :user:`asmodehn` for writing the tests to reproduce this issue. 2.16.2 (2018-10-30) ******************* Bug fixes: - Prevent warning about importing from ``collections`` on Python 3.7 (:issue:`1027`). Thanks :user:`nkonin` for reporting and :user:`jmargeta` for the PR. 2.16.1 (2018-10-17) ******************* Bug fixes: - Remove spurious warning about implicit collection handling (:issue:`998`). Thanks :user:`lalvarezguillen` for reporting. 2.16.0 (2018-10-10) ******************* Bug fixes: - Allow username without password in basic auth part of the url in ``fields.Url`` (:pr:`982`). Thanks user:`alefnula` for the PR. Other changes: - Drop support for Python 3.3 (:pr:`987`). 2.15.6 (2018-09-20) ******************* Bug fixes: - Prevent ``TypeError`` when a non-collection is passed to a ``Schema`` with ``many=True``. Instead, raise ``ValidationError`` with ``{'_schema': ['Invalid input type.']}`` (:issue:`906`). - Fix ``root`` attribute for nested container fields on list on inheriting schemas (:issue:`956`). Thanks :user:`bmcbu` for reporting. These fixes were backported from 3.0.0b15 and 3.0.0b16. 2.15.5 (2018-09-15) ******************* Bug fixes: - Handle empty SQLAlchemy lazy lists gracefully when dumping (:issue:`948`). Thanks :user:`vke-code` for the catch and :user:`YuriHeupa` for the patch. 2.15.4 (2018-08-04) ******************* Bug fixes: - Respect ``load_from`` when reporting errors for ``@validates('field_name')`` (:issue:`748`). Thanks :user:`m-novikov` for the catch and patch. 2.15.3 (2018-05-20) ******************* Bug fixes: - Fix passing ``only`` as a string to ``nested`` when the passed field defines ``dump_to`` (:issue:`800`, :issue:`822`). Thanks :user:`deckar01` for the catch and patch. 2.15.2 (2018-05-10) ******************* Bug fixes: - Fix a race condition in validation when concurrent threads use the same ``Schema`` instance (:issue:`783`). Thanks :user:`yupeng0921` and :user:`lafrech` for the fix. - Fix serialization behavior of ``fields.List(fields.Integer(as_string=True))`` (:issue:`788`). Thanks :user:`cactus` for reporting and :user:`lafrech` for the fix. - Fix behavior of ``exclude`` parameter when passed from parent to nested schemas (:issue:`728`). Thanks :user:`timc13` for reporting and :user:`deckar01` for the fix. 2.15.1 (2018-04-25) ******************* Bug fixes: - :cve:`CVE-2018-17175`: Fix behavior when an empty list is passed as the ``only`` argument (:issue:`772`). Thanks :user:`deckar01` for reporting and thanks :user:`lafrech` for the fix. 2.15.0 (2017-12-02) ******************* Bug fixes: - Handle ``UnicodeDecodeError`` when deserializing ``bytes`` with a ``String`` field (:issue:`650`). Thanks :user:`dan-blanchard` for the suggestion and thanks :user:`4lissonsilveira` for the PR. 2.14.0 (2017-10-23) ******************* Features: - Add ``require_tld`` parameter to ``validate.URL`` (:issue:`664`). Thanks :user:`sduthil` for the suggestion and the PR. 2.13.6 (2017-08-16) ******************* Bug fixes: - Fix serialization of types that implement `__getitem__` (:issue:`669`). Thanks :user:`MichalKononenko`. 2.13.5 (2017-04-12) ******************* Bug fixes: - Fix validation of iso8601-formatted dates (:issue:`556`). Thanks :user:`lafrech` for reporting. 2.13.4 (2017-03-19) ******************* Bug fixes: - Fix symmetry of serialization and deserialization behavior when passing a dot-delimited path to the ``attribute`` parameter of fields (:issue:`450`). Thanks :user:`itajaja` for reporting. 2.13.3 (2017-03-11) ******************* Bug fixes: - Restore backwards-compatibility of ``SchemaOpts`` constructor (:issue:`597`). Thanks :user:`Wesmania` for reporting and thanks :user:`frol` for the fix. 2.13.2 (2017-03-10) ******************* Bug fixes: - Fix inheritance of ``ordered`` option when ``Schema`` subclasses define ``class Meta`` (:issue:`593`). Thanks :user:`frol`. Support: - Update contributing docs. 2.13.1 (2017-03-04) ******************* Bug fixes: - Fix sorting on Schema subclasses when ``ordered=True`` (:issue:`592`). Thanks :user:`frol`. 2.13.0 (2017-02-18) ******************* Features: - Minor optimizations (:issue:`577`). Thanks :user:`rowillia` for the PR. 2.12.2 (2017-01-30) ******************* Bug fixes: - Unbound fields return `None` rather returning the field itself. This fixes a corner case introduced in :issue:`572`. Thanks :user:`touilleMan` for reporting and :user:`YuriHeupa` for the fix. 2.12.1 (2017-01-23) ******************* Bug fixes: - Fix behavior when a ``Nested`` field is composed within a ``List`` field (:issue:`572`). Thanks :user:`avish` for reporting and :user:`YuriHeupa` for the PR. 2.12.0 (2017-01-22) ******************* Features: - Allow passing nested attributes (e.g. ``'child.field'``) to the ``dump_only`` and ``load_only`` parameters of ``Schema`` (:issue:`572`). Thanks :user:`YuriHeupa` for the PR. - Add ``schemes`` parameter to ``fields.URL`` (:issue:`574`). Thanks :user:`mosquito` for the PR. 2.11.1 (2017-01-08) ******************* Bug fixes: - Allow ``strict`` class Meta option to be overridden by constructor (:issue:`550`). Thanks :user:`douglas-treadwell` for reporting and thanks :user:`podhmo` for the PR. 2.11.0 (2017-01-08) ******************* Features: - Import ``marshmallow.fields`` in ``marshmallow/__init__.py`` to save an import when importing the ``marshmallow`` module (:issue:`557`). Thanks :user:`mindojo-victor`. Support: - Documentation: Improve example in "Validating Original Input Data" (:issue:`558`). Thanks :user:`altaurog`. - Test against Python 3.6. 2.10.5 (2016-12-19) ******************* Bug fixes: - Reset user-defined kwargs passed to ``ValidationError`` on each ``Schema.load`` call (:issue:`565`). Thanks :user:`jbasko` for the catch and patch. Support: - Tests: Fix redefinition of ``test_utils.test_get_value()`` (:issue:`562`). Thanks :user:`nelfin`. 2.10.4 (2016-11-18) ******************* Bug fixes: - `Function` field works with callables that use Python 3 type annotations (:issue:`540`). Thanks :user:`martinstein` for reporting and thanks :user:`sabinem`, :user:`lafrech`, and :user:`maximkulkin` for the work on the PR. 2.10.3 (2016-10-02) ******************* Bug fixes: - Fix behavior for serializing missing data with ``Number`` fields when ``as_string=True`` is passed (:issue:`538`). Thanks :user:`jessemyers` for reporting. 2.10.2 (2016-09-25) ******************* Bug fixes: - Use fixed-point notation rather than engineering notation when serializing with ``Decimal`` (:issue:`534`). Thanks :user:`gdub`. - Fix UUID validation on serialization and deserialization of ``uuid.UUID`` objects (:issue:`532`). Thanks :user:`pauljz`. 2.10.1 (2016-09-14) ******************* Bug fixes: - Fix behavior when using ``validate.Equal(False)`` (:issue:`484`). Thanks :user:`pktangyue` for reporting and thanks :user:`tuukkamustonen` for the fix. - Fix ``strict`` behavior when errors are raised in ``pre_dump``/``post_dump`` processors (:issue:`521`). Thanks :user:`tvuotila` for the catch and patch. - Fix validation of nested fields on dumping (:issue:`528`). Thanks again :user:`tvuotila`. 2.10.0 (2016-09-05) ******************* Features: - Errors raised by pre/post-load/dump methods will be added to a schema's errors dictionary (:issue:`472`). Thanks :user:`dbertouille` for the suggestion and for the PR. 2.9.1 (2016-07-21) ****************** Bug fixes: - Fix serialization of ``datetime.time`` objects with microseconds (:issue:`464`). Thanks :user:`Tim-Erwin` for reporting and thanks :user:`vuonghv` for the fix. - Make ``@validates`` consistent with field validator behavior: if validation fails, the field will not be included in the deserialized output (:issue:`391`). Thanks :user:`martinstein` for reporting and thanks :user:`vuonghv` for the fix. 2.9.0 (2016-07-06) ****************** - ``Decimal`` field coerces input values to a string before deserializing to a `decimal.Decimal` object in order to avoid transformation of float values under 12 significant digits (:issue:`434`, :issue:`435`). Thanks :user:`davidthornton` for the PR. 2.8.0 (2016-06-23) ****************** Features: - Allow ``only`` and ``exclude`` parameters to take nested fields, using dot-delimited syntax (e.g. ``only=['blog.author.email']``) (:issue:`402`). Thanks :user:`Tim-Erwin` and :user:`deckar01` for the discussion and implementation. Support: - Update tasks.py for compatibility with invoke>=0.13.0. Thanks :user:`deckar01`. 2.7.3 (2016-05-05) ****************** - Make ``field.parent`` and ``field.name`` accessible to ``on_bind_field`` (:issue:`449`). Thanks :user:`immerrr`. 2.7.2 (2016-04-27) ****************** No code changes in this release. This is a reupload in order to distribute an sdist for the last hotfix release. See :issue:`443`. Support: - Update license entry in setup.py to fix RPM distributions (:issue:`433`). Thanks :user:`rrajaravi` for reporting. 2.7.1 (2016-04-08) ****************** Bug fixes: - Only add Schemas to class registry if a class name is provided. This allows Schemas to be constructed dynamically using the ``type`` constructor without getting added to the class registry (which is useful for saving memory). 2.7.0 (2016-04-04) ****************** Features: - Make context available to ``Nested`` field's ``on_bind_field`` method (:issue:`408`). Thanks :user:`immerrr` for the PR. - Pass through user ``ValidationError`` kwargs (:issue:`418`). Thanks :user:`russelldavies` for helping implement this. Other changes: - Remove unused attributes ``root``, ``parent``, and ``name`` from ``SchemaABC`` (:issue:`410`). Thanks :user:`Tim-Erwin` for the PR. 2.6.1 (2016-03-17) ****************** Bug fixes: - Respect ``load_from`` when reporting errors for nested required fields (:issue:`414`). Thanks :user:`yumike`. 2.6.0 (2016-02-01) ****************** Features: - Add ``partial`` argument to ``Schema.validate`` (:issue:`379`). Thanks :user:`tdevelioglu` for the PR. - Add ``equal`` argument to ``validate.Length``. Thanks :user:`daniloakamine`. - Collect all validation errors for each item deserialized by a ``List`` field (:issue:`345`). Thanks :user:`maximkulkin` for the report and the PR. 2.5.0 (2016-01-16) ****************** Features: - Allow a tuple of field names to be passed as the ``partial`` argument to ``Schema.load`` (:issue:`369`). Thanks :user:`tdevelioglu` for the PR. - Add ``schemes`` argument to ``validate.URL`` (:issue:`356`). 2.4.2 (2015-12-08) ****************** Bug fixes: - Prevent duplicate error messages when validating nested collections (:issue:`360`). Thanks :user:`alexmorken` for the catch and patch. 2.4.1 (2015-12-07) ****************** Bug fixes: - Serializing an iterator will not drop the first item (:issue:`343`, :issue:`353`). Thanks :user:`jmcarp` for the patch. Thanks :user:`edgarallang` and :user:`jmcarp` for reporting. 2.4.0 (2015-12-06) ****************** Features: - Add ``skip_on_field_errors`` parameter to ``validates_schema`` (:issue:`323`). Thanks :user:`jjvattamattom` for the suggestion and :user:`d-sutherland` for the PR. Bug fixes: - Fix ``FormattedString`` serialization (:issue:`348`). Thanks :user:`acaird` for reporting. - Fix ``@validates`` behavior when used when ``attribute`` is specified and ``strict=True`` (:issue:`350`). Thanks :user:`density` for reporting. 2.3.0 (2015-11-22) ****************** Features: - Add ``dump_to`` parameter to fields (:issue:`310`). Thanks :user:`ShayanArmanPercolate` for the suggestion. Thanks :user:`franciscod` and :user:`ewang` for the PRs. - The ``deserialize`` function passed to ``fields.Function`` can optionally receive a ``context`` argument (:issue:`324`). Thanks :user:`DamianHeard`. - The ``serialize`` function passed to ``fields.Function`` is optional (:issue:`325`). Thanks again :user:`DamianHeard`. - The ``serialize`` function passed to ``fields.Method`` is optional (:issue:`329`). Thanks :user:`justanr`. Deprecation/Removal: - The ``func`` argument of ``fields.Function`` has been renamed to ``serialize``. - The ``method_name`` argument of ``fields.Method`` has been renamed to ``serialize``. ``func`` and ``method_name`` are still present for backwards-compatibility, but they will both be removed in marshmallow 3.0. 2.2.1 (2015-11-11) ****************** Bug fixes: - Skip field validators for fields that aren't included in ``only`` (:issue:`320`). Thanks :user:`carlos-alberto` for reporting and :user:`eprikazc` for the PR. 2.2.0 (2015-10-26) ****************** Features: - Add support for partial deserialization with the ``partial`` argument to ``Schema`` and ``Schema.load`` (:issue:`290`). Thanks :user:`taion`. Deprecation/Removals: - ``Query`` and ``QuerySelect`` fields are removed. - Passing of strings to ``required`` and ``allow_none`` is removed. Pass the ``error_messages`` argument instead. Support: - Add example of Schema inheritance in docs (:issue:`225`). Thanks :user:`martinstein` for the suggestion and :user:`juanrossi` for the PR. - Add "Customizing Error Messages" section to custom fields docs. 2.1.3 (2015-10-18) ****************** Bug fixes: - Fix serialization of collections for which ``iter`` will modify position, e.g. Pymongo cursors (:issue:`303`). Thanks :user:`Mise` for the catch and patch. 2.1.2 (2015-10-14) ****************** Bug fixes: - Fix passing data to schema validator when using ``@validates_schema(many=True)`` (:issue:`297`). Thanks :user:`d-sutherland` for reporting. - Fix usage of ``@validates`` with a nested field when ``many=True`` (:issue:`298`). Thanks :user:`nelfin` for the catch and patch. 2.1.1 (2015-10-07) ****************** Bug fixes: - ``Constant`` field deserializes to its value regardless of whether its field name is present in input data (:issue:`291`). Thanks :user:`fayazkhan` for reporting. 2.1.0 (2015-09-30) ****************** Features: - Add ``Dict`` field for arbitrary mapping data (:issue:`251`). Thanks :user:`dwieeb` for adding this and :user:`Dowwie` for the suggestion. - Add ``Field.root`` property, which references the field's Schema. Deprecation/Removals: - The ``extra`` param of ``Schema`` is deprecated. Add extra data in a ``post_load`` method instead. - ``UnmarshallingError`` and ``MarshallingError`` are removed. Bug fixes: - Fix storing multiple schema-level validation errors (:issue:`287`). Thanks :user:`evgeny-sureev` for the patch. - If ``missing=None`` on a field, ``allow_none`` will be set to ``True``. Other changes: - A ``List's`` inner field will have the list field set as its parent. Use ``root`` to access the ``Schema``. 2.0.0 (2015-09-25) ****************** Features: - Make error messages configurable at the class level and instance level (``Field.default_error_messages`` attribute and ``error_messages`` parameter, respectively). Deprecation/Removals: - Remove ``make_object``. Use a ``post_load`` method instead (:issue:`277`). - Remove the ``error`` parameter and attribute of ``Field``. - Passing string arguments to ``required`` and ``allow_none`` is deprecated. Pass the ``error_messages`` argument instead. **This API will be removed in version 2.2**. - Remove ``Arbitrary``, ``Fixed``, and ``Price`` fields (:issue:`86`). Use ``Decimal`` instead. - Remove ``Select`` / ``Enum`` fields (:issue:`135`). Use the ``OneOf`` validator instead. Bug fixes: - Fix error format for ``Nested`` fields when ``many=True``. Thanks :user:`alexmorken`. - ``pre_dump`` methods are invoked before implicit field creation. Thanks :user:`makmanalp` for reporting. - Return correct "required" error message for ``Nested`` field. - The ``only`` argument passed to a ``Schema`` is bounded by the ``fields`` option (:issue:`183`). Thanks :user:`lustdante` for the suggestion. Changes from 2.0.0rc2: - ``error_handler`` and ``accessor`` options are replaced with the ``handle_error`` and ``get_attribute`` methods :issue:`284`. - Remove ``marshmallow.compat.plain_function`` since it is no longer used. - Non-collection values are invalid input for ``List`` field (:issue:`231`). Thanks :user:`density` for reporting. - Bug fix: Prevent infinite loop when validating a required, self-nested field. Thanks :user:`Bachmann1234` for the fix. 2.0.0rc2 (2015-09-16) ********************* Deprecation/Removals: - ``make_object`` is deprecated. Use a ``post_load`` method instead (:issue:`277`). **This method will be removed in the final 2.0 release**. - ``Schema.accessor`` and ``Schema.error_handler`` decorators are deprecated. Define the ``accessor`` and ``error_handler`` class Meta options instead. Bug fixes: - Allow non-field names to be passed to ``ValidationError`` (:issue:`273`). Thanks :user:`evgeny-sureev` for the catch and patch. Changes from 2.0.0rc1: - The ``raw`` parameter of the ``pre_*``, ``post_*``, ``validates_schema`` decorators was renamed to ``pass_many`` (:issue:`276`). - Add ``pass_original`` parameter to ``post_load`` and ``post_dump`` (:issue:`216`). - Methods decorated with the ``pre_*``, ``post_*``, and ``validates_*`` decorators must be instance methods. Class methods and instance methods are not supported at this time. 2.0.0rc1 (2015-09-13) ********************* Features: - *Backwards-incompatible*: ``fields.Field._deserialize`` now takes ``attr`` and ``data`` as arguments (:issue:`172`). Thanks :user:`alexmic` and :user:`kevinastone` for the suggestion. - Allow a ``Field's`` ``attribute`` to be modified during deserialization (:issue:`266`). Thanks :user:`floqqi`. - Allow partially-valid data to be returned for ``Nested`` fields (:issue:`269`). Thanks :user:`jomag` for the suggestion. - Add ``Schema.on_bind_field`` hook which allows a ``Schema`` to modify its fields when they are bound. - Stricter validation of string, boolean, and number fields (:issue:`231`). Thanks :user:`touilleMan` for the suggestion. - Improve consistency of error messages. Deprecation/Removals: - ``Schema.validator``, ``Schema.preprocessor``, and ``Schema.data_handler`` are removed. Use ``validates_schema``, ``pre_load``, and ``post_dump`` instead. - ``QuerySelect`` and ``QuerySelectList`` are deprecated (:issue:`227`). **These fields will be removed in version 2.1.** - ``utils.get_callable_name`` is removed. Bug fixes: - If a date format string is passed to a ``DateTime`` field, it is always used for deserialization (:issue:`248`). Thanks :user:`bartaelterman` and :user:`praveen-p`. Support: - Documentation: Add "Using Context" section to "Extending Schemas" page (:issue:`224`). - Include tests and docs in release tarballs (:issue:`201`). - Test against Python 3.5. 2.0.0b5 (2015-08-23) ******************** Features: - If a field corresponds to a callable attribute, it will be called upon serialization. Thanks :user:`alexmorken`. - Add ``load_only`` and ``dump_only`` ``class Meta`` options. Thanks :user:`kelvinhammond`. - If a ``Nested`` field is required, recursively validate any required fields in the nested schema (:issue:`235`). Thanks :user:`max-orhai`. - Improve error message if a list of dicts is not passed to a ``Nested`` field for which ``many=True``. Thanks again :user:`max-orhai`. Bug fixes: - ``make_object`` is only called after all validators and postprocessors have finished (:issue:`253`). Thanks :user:`sunsongxp` for reporting. - If an invalid type is passed to ``Schema`` and ``strict=False``, store a ``_schema`` error in the errors dict rather than raise an exception (:issue:`261`). Thanks :user:`density` for reporting. Other changes: - ``make_object`` is only called when input data are completely valid (:issue:`243`). Thanks :user:`kissgyorgy` for reporting. - Change default error messages for ``URL`` and ``Email`` validators so that they don't include user input (:issue:`255`). - ``Email`` validator permits email addresses with non-ASCII characters, as per RFC 6530 (:issue:`221`). Thanks :user:`lextoumbourou` for reporting and :user:`mwstobo` for sending the patch. 2.0.0b4 (2015-07-07) ******************** Features: - ``List`` field respects the ``attribute`` argument of the inner field. Thanks :user:`jmcarp`. - The ``container`` field ``List`` field has access to its parent ``Schema`` via its ``parent`` attribute. Thanks again :user:`jmcarp`. Deprecation/Removals: - Legacy validator functions have been removed (:issue:`73`). Use the class-based validators in ``marshmallow.validate`` instead. Bug fixes: - ``fields.Nested`` correctly serializes nested ``sets`` (:issue:`233`). Thanks :user:`traut`. Changes from 2.0.0b3: - If ``load_from`` is used on deserialization, the value of ``load_from`` is used as the key in the errors dict (:issue:`232`). Thanks :user:`alexmorken`. 2.0.0b3 (2015-06-14) ********************* Features: - Add ``marshmallow.validates_schema`` decorator for defining schema-level validators (:issue:`116`). - Add ``marshmallow.validates`` decorator for defining field validators as Schema methods (:issue:`116`). Thanks :user:`philtay`. - Performance improvements. - Defining ``__marshallable__`` on complex objects is no longer necessary. - Add ``fields.Constant``. Thanks :user:`kevinastone`. Deprecation/Removals: - Remove ``skip_missing`` class Meta option. By default, missing inputs are excluded from serialized output (:issue:`211`). - Remove optional ``context`` parameter that gets passed to methods for ``Method`` fields. - ``Schema.validator`` is deprecated. Use ``marshmallow.validates_schema`` instead. - ``utils.get_func_name`` is removed. Use ``utils.get_callable_name`` instead. Bug fixes: - Fix serializing values from keyed tuple types (regression of :issue:`28`). Thanks :user:`makmanalp` for reporting. Other changes: - Remove unnecessary call to ``utils.get_value`` for ``Function`` and ``Method`` fields (:issue:`208`). Thanks :user:`jmcarp`. - Serializing a collection without passing ``many=True`` will not result in an error. Be very careful to pass the ``many`` argument when necessary. Support: - Documentation: Update Flask and Peewee examples. Update Quickstart. Changes from 2.0.0b2: - ``Boolean`` field serializes ``None`` to ``None``, for consistency with other fields (:issue:`213`). Thanks :user:`cmanallen` for reporting. - Bug fix: ``load_only`` fields do not get validated during serialization. - Implicit passing of original, raw data to Schema validators is removed. Use ``@marshmallow.validates_schema(pass_original=True)`` instead. 2.0.0b2 (2015-05-03) ******************** Features: - Add useful ``__repr__`` methods to validators (:issue:`204`). Thanks :user:`philtay`. - *Backwards-incompatible*: By default, ``NaN``, ``Infinity``, and ``-Infinity`` are invalid values for ``fields.Decimal``. Pass ``allow_nan=True`` to allow these values. Thanks :user:`philtay`. Changes from 2.0.0b1: - Fix serialization of ``None`` for ``Time``, ``TimeDelta``, and ``Date`` fields (a regression introduced in 2.0.0a1). Includes bug fixes from 1.2.6. 2.0.0b1 (2015-04-26) ******************** Features: - Errored fields will not appear in (de)serialized output dictionaries (:issue:`153`, :issue:`202`). - Instantiate ``OPTIONS_CLASS`` in ``SchemaMeta``. This makes ``Schema.opts`` available in metaclass methods. It also causes validation to occur earlier (upon ``Schema`` class declaration rather than instantiation). - Add ``SchemaMeta.get_declared_fields`` class method to support adding additional declared fields. Deprecation/Removals: - Remove ``allow_null`` parameter of ``fields.Nested`` (:issue:`203`). Changes from 2.0.0a1: - Fix serialization of `None` for ``fields.Email``. 2.0.0a1 (2015-04-25) ******************** Features: - *Backwards-incompatible*: When ``many=True``, the errors dictionary returned by ``dump`` and ``load`` will be keyed on the indices of invalid items in the (de)serialized collection (:issue:`75`). Add ``index_errors=False`` on a Schema's ``class Meta`` options to disable this behavior. - *Backwards-incompatible*: By default, fields will raise a ValidationError if the input is ``None``. The ``allow_none`` parameter can override this behavior. - *Backwards-incompatible*: A ``Field's`` ``default`` parameter is only used if explicitly set and the field's value is missing in the input to `Schema.dump`. If not set, the key will not be present in the serialized output for missing values . This is the behavior for *all* fields. ``fields.Str`` no longer defaults to ``''``, ``fields.Int`` no longer defaults to ``0``, etc. (:issue:`199`). Thanks :user:`jmcarp` for the feedback. - In ``strict`` mode, a ``ValidationError`` is raised. Error messages are accessed via the ``ValidationError's`` ``messages`` attribute (:issue:`128`). - Add ``allow_none`` parameter to ``fields.Field``. If ``False`` (the default), validation fails when the field's value is ``None`` (:issue:`76`, :issue:`111`). If ``allow_none`` is ``True``, ``None`` is considered valid and will deserialize to ``None``. - Schema-level validators can store error messages for multiple fields (:issue:`118`). Thanks :user:`ksesong` for the suggestion. - Add ``pre_load``, ``post_load``, ``pre_dump``, and ``post_dump`` Schema method decorators for defining pre- and post- processing routines (:issue:`153`, :issue:`179`). Thanks :user:`davidism`, :user:`taion`, and :user:`jmcarp` for the suggestions and feedback. Thanks :user:`taion` for the implementation. - Error message for ``required`` validation is configurable. (:issue:`78`). Thanks :user:`svenstaro` for the suggestion. Thanks :user:`0xDCA` for the implementation. - Add ``load_from`` parameter to fields (:issue:`125`). Thanks :user:`hakjoon`. - Add ``load_only`` and ``dump_only`` parameters to fields (:issue:`61`, :issue:`87`). Thanks :user:`philtay`. - Add `missing` parameter to fields (:issue:`115`). Thanks :user:`philtay`. - Schema validators can take an optional ``raw_data`` argument which contains raw input data, incl. data not specified in the schema (:issue:`127`). Thanks :user:`ryanlowe0`. - Add ``validate.OneOf`` (:issue:`135`) and ``validate.ContainsOnly`` (:issue:`149`) validators. Thanks :user:`philtay`. - Error messages for validators can be interpolated with `{input}` and other values (depending on the validator). - ``fields.TimeDelta`` always serializes to an integer value in order to avoid rounding errors (:issue:`105`). Thanks :user:`philtay`. - Add ``include`` class Meta option to support field names which are Python keywords (:issue:`139`). Thanks :user:`nickretallack` for the suggestion. - ``exclude`` parameter is respected when used together with ``only`` parameter (:issue:`165`). Thanks :user:`lustdante` for the catch and patch. - ``fields.List`` works as expected with generators and sets (:issue:`185`). Thanks :user:`sergey-aganezov-jr`. Deprecation/Removals: - ``MarshallingError`` and ``UnmarshallingError`` error are deprecated in favor of a single ``ValidationError`` (:issue:`160`). - ``context`` argument passed to Method fields is deprecated. Use ``self.context`` instead (:issue:`184`). - Remove ``ForcedError``. - Remove support for generator functions that yield validators (:issue:`74`). Plain generators of validators are still supported. - The ``Select/Enum`` field is deprecated in favor of using ``validate.OneOf`` validator (:issue:`135`). - Remove legacy, pre-1.0 API (``Schema.data`` and ``Schema.errors`` properties) (:issue:`73`). - Remove ``null`` value. Other changes: - ``Marshaller``, ``Unmarshaller`` were moved to ``marshmallow.marshalling``. These should be considered private API (:issue:`129`). - Make ``allow_null=True`` the default for ``Nested`` fields. This will make ``None`` serialize to ``None`` rather than a dictionary with empty values (:issue:`132`). Thanks :user:`nickrellack` for the suggestion. 1.2.6 (2015-05-03) ****************** Bug fixes: - Fix validation error message for ``fields.Decimal``. - Allow error message for ``fields.Boolean`` to be customized with the ``error`` parameter (like other fields). 1.2.5 (2015-04-25) ****************** Bug fixes: - Fix validation of invalid types passed to a ``Nested`` field when ``many=True`` (:issue:`188`). Thanks :user:`juanrossi` for reporting. Support: - Fix pep8 dev dependency for flake8. Thanks :user:`taion`. 1.2.4 (2015-03-22) ****************** Bug fixes: - Fix behavior of ``as_string`` on ``fields.Integer`` (:issue:`173`). Thanks :user:`taion` for the catch and patch. Other changes: - Remove dead code from ``fields.Field``. Thanks :user:`taion`. Support: - Correction to ``_postprocess`` method in docs. Thanks again :user:`taion`. 1.2.3 (2015-03-15) ****************** Bug fixes: - Fix inheritance of ``ordered`` class Meta option (:issue:`162`). Thanks :user:`stephenfin` for reporting. 1.2.2 (2015-02-23) ****************** Bug fixes: - Fix behavior of ``skip_missing`` and ``accessor`` options when ``many=True`` (:issue:`137`). Thanks :user:`3rdcycle`. - Fix bug that could cause an ``AttributeError`` when nesting schemas with schema-level validators (:issue:`144`). Thanks :user:`vovanbo` for reporting. 1.2.1 (2015-01-11) ****************** Bug fixes: - A ``Schema's`` ``error_handler``--if defined--will execute if ``Schema.validate`` returns validation errors (:issue:`121`). - Deserializing `None` returns `None` rather than raising an ``AttributeError`` (:issue:`123`). Thanks :user:`RealSalmon` for the catch and patch. 1.2.0 (2014-12-22) ****************** Features: - Add ``QuerySelect`` and ``QuerySelectList`` fields (:issue:`84`). - Convert validators in ``marshmallow.validate`` into class-based callables to make them easier to use when declaring fields (:issue:`85`). - Add ``Decimal`` field which is safe to use when dealing with precise numbers (:issue:`86`). Thanks :user:`philtay` for these contributions. Bug fixes: - ``Date`` fields correctly deserializes to a ``datetime.date`` object when ``python-dateutil`` is not installed (:issue:`79`). Thanks :user:`malexer` for the catch and patch. - Fix bug that raised an ``AttributeError`` when using a class-based validator. - Fix ``as_string`` behavior of Number fields when serializing to default value. - Deserializing ``None`` or the empty string with either a ``DateTime``, ``Date``, ``Time`` or ``TimeDelta`` results in the correct unmarshalling errors (:issue:`96`). Thanks :user:`svenstaro` for reporting and helping with this. - Fix error handling when deserializing invalid UUIDs (:issue:`106`). Thanks :user:`vesauimonen` for the catch and patch. - ``Schema.loads`` correctly defaults to use the value of ``self.many`` rather than defaulting to ``False`` (:issue:`108`). Thanks :user:`davidism` for the catch and patch. - Validators, data handlers, and preprocessors are no longer shared between schema subclasses (:issue:`88`). Thanks :user:`amikholap` for reporting. - Fix error handling when passing a ``dict`` or ``list`` to a ``ValidationError`` (:issue:`110`). Thanks :user:`ksesong` for reporting. Deprecation: - The validator functions in the ``validate`` module are deprecated in favor of the class-based validators (:issue:`85`). - The ``Arbitrary``, ``Price``, and ``Fixed`` fields are deprecated in favor of the ``Decimal`` field (:issue:`86`). Support: - Update docs theme. - Update contributing docs (:issue:`77`). - Fix namespacing example in "Extending Schema" docs. Thanks :user:`Ch00k`. - Exclude virtualenv directories from syntax checking (:issue:`99`). Thanks :user:`svenstaro`. 1.1.0 (2014-12-02) ****************** Features: - Add ``Schema.validate`` method which validates input data against a schema. Similar to ``Schema.load``, but does not call ``make_object`` and only returns the errors dictionary. - Add several validation functions to the ``validate`` module. Thanks :user:`philtay`. - Store field name and instance on exceptions raised in ``strict`` mode. Bug fixes: - Fix serializing dictionaries when field names are methods of ``dict`` (e.g. ``"items"``). Thanks :user:`rozenm` for reporting. - If a Nested field is passed ``many=True``, ``None`` serializes to an empty list. Thanks :user:`nickretallack` for reporting. - Fix behavior of ``many`` argument passed to ``dump`` and ``load``. Thanks :user:`svenstaro` for reporting and helping with this. - Fix ``skip_missing`` behavior for ``String`` and ``List`` fields. Thanks :user:`malexer` for reporting. - Fix compatibility with python-dateutil 2.3. - More consistent error messages across ``DateTime``, ``TimeDelta``, ``Date``, and ``Time`` fields. Support: - Update Flask and Peewee examples. 1.0.1 (2014-11-18) ****************** Hotfix release. - Ensure that errors dictionary is correctly cleared on each call to ``Schema.dump`` and ``Schema.load``. 1.0.0 (2014-11-16) ****************** Adds new features, speed improvements, better error handling, and updated documentation. - Add ``skip_missing`` ``class Meta`` option. - A field's ``default`` may be a callable. - Allow accessor function to be configured via the ``Schema.accessor`` decorator or the ``__accessor__`` class member. - ``URL`` and ``Email`` fields are validated upon serialization. - ``dump`` and ``load`` can receive the ``many`` argument. - Move a number of utility functions from fields.py to utils.py. - More useful ``repr`` for ``Field`` classes. - If a field's default is ``fields.missing`` and its serialized value is ``None``, it will not be included in the final serialized result. - Schema.dumps no longer coerces its result to a binary string on Python 3. - *Backwards-incompatible*: Schema output is no longer an ``OrderedDict`` by default. If you want ordered field output, you must explicitly set the ``ordered`` option to ``True``. - *Backwards-incompatible*: ``error`` parameter of the ``Field`` constructor is deprecated. Raise a ``ValidationError`` instead. - Expanded test coverage. - Updated docs. 1.0.0-a (2014-10-19) ******************** Major reworking and simplification of the public API, centered around support for deserialization, improved validation, and a less stateful ``Schema`` class. * Rename ``Serializer`` to ``Schema``. * Support for deserialization. * Use the ``Schema.dump`` and ``Schema.load`` methods for serializing and deserializing, respectively. * *Backwards-incompatible*: Remove ``Serializer.json`` and ``Serializer.to_json``. Use ``Schema.dumps`` instead. * Reworked fields interface. * *Backwards-incompatible*: ``Field`` classes implement ``_serialize`` and ``_deserialize`` methods. ``serialize`` and ``deserialize`` comprise the public API for a ``Field``. ``Field.format`` and ``Field.output`` have been removed. * Add ``exceptions.ForcedError`` which allows errors to be raised during serialization (instead of storing errors in the ``errors`` dict). * *Backwards-incompatible*: ``DateTime`` field serializes to ISO8601 format by default (instead of RFC822). * *Backwards-incompatible*: Remove ``Serializer.factory`` method. It is no longer necessary with the ``dump`` method. * *Backwards-incompatible*: Allow nesting a serializer within itself recursively. Use ``exclude`` or ``only`` to prevent infinite recursion. * *Backwards-incompatible*: Multiple errors can be stored for a single field. The errors dictionary returned by ``load`` and ``dump`` have lists of error messages keyed by field name. * Remove ``validated`` decorator. Validation occurs within ``Field`` methods. * ``Function`` field raises a ``ValueError`` if an uncallable object is passed to its constructor. * ``Nested`` fields inherit context from their parent. * Add ``Schema.preprocessor`` and ``Schema.validator`` decorators for registering preprocessing and schema-level validation functions respectively. * Custom error messages can be specified by raising a ``ValidationError`` within a validation function. * Extra keyword arguments passed to a Field are stored as metadata. * Fix ordering of field output. * Fix behavior of the ``required`` parameter on ``Nested`` fields. * Fix serializing keyed tuple types (e.g. ``namedtuple``) with ``class Meta`` options. * Fix default value for ``Fixed`` and ``Price`` fields. * Fix serialization of binary strings. * ``Schemas`` can inherit fields from non-``Schema`` base classes (e.g. mixins). Also, fields are inherited according to the MRO (rather than recursing over base classes). Thanks :user:`jmcarp`. * Add ``Str``, ``Bool``, and ``Int`` field class aliases. 0.7.0 (2014-06-22) ****************** * Add ``Serializer.error_handler`` decorator that registers a custom error handler. * Add ``Serializer.data_handler`` decorator that registers data post-processing callbacks. * *Backwards-incompatible*: ``process_data`` method is deprecated. Use the ``data_handler`` decorator instead. * Fix bug that raised error when passing ``extra`` data together with ``many=True``. Thanks :user:`buttsicles` for reporting. * If ``required=True`` validation is violated for a given ``Field``, it will raise an error message that is different from the message specified by the ``error`` argument. Thanks :user:`asteinlein`. * More generic error message raised when required field is missing. * ``validated`` decorator should only wrap a ``Field`` class's ``output`` method. 0.6.0 (2014-06-03) ****************** * Fix bug in serializing keyed tuple types, e.g. ``namedtuple`` and ``KeyedTuple``. * Nested field can load a serializer by its class name as a string. This makes it easier to implement 2-way nesting. * Make ``Serializer.data`` override-able. 0.5.5 (2014-05-02) ****************** * Add ``Serializer.factory`` for creating a factory function that returns a Serializer instance. * ``MarshallingError`` stores its underlying exception as an instance variable. This is useful for inspecting errors. * ``fields.Select`` is aliased to ``fields.Enum``. * Add ``fields.__all__`` and ``marshmallow.__all__`` so that the modules can be more easily extended. * Expose ``Serializer.OPTIONS_CLASS`` as a class variable so that options defaults can be overridden. * Add ``Serializer.process_data`` hook that allows subclasses to manipulate the final output data. 0.5.4 (2014-04-17) ****************** * Add ``json_module`` class Meta option. * Add ``required`` option to fields . Thanks :user:`DeaconDesperado`. * Tested on Python 3.4 and PyPy. 0.5.3 (2014-03-02) ****************** * Fix ``Integer`` field default. It is now ``0`` instead of ``0.0``. Thanks :user:`kalasjocke`. * Add ``context`` param to ``Serializer``. Allows accessing arbitrary objects in ``Function`` and ``Method`` fields. * ``Function`` and ``Method`` fields raise ``MarshallingError`` if their argument is uncallable. 0.5.2 (2014-02-10) ****************** * Enable custom field validation via the ``validate`` parameter. * Add ``utils.from_rfc`` for parsing RFC datestring to Python datetime object. 0.5.1 (2014-02-02) ****************** * Avoid unnecessary attribute access in ``utils.to_marshallable_type`` for improved performance. * Fix RFC822 formatting for localized datetimes. 0.5.0 (2013-12-29) ****************** * Can customize validation error messages by passing the ``error`` parameter to a field. * *Backwards-incompatible*: Rename ``fields.NumberField`` -> ``fields.Number``. * Add ``fields.Select``. Thanks :user:`ecarreras`. * Support nesting a Serializer within itself by passing ``"self"`` into ``fields.Nested`` (only up to depth=1). * *Backwards-incompatible*: No implicit serializing of collections. Must set ``many=True`` if serializing to a list. This ensures that marshmallow handles singular objects correctly, even if they are iterable. * If Nested field ``only`` parameter is a field name, only return a single value for the nested object (instead of a dict) or a flat list of values. * Improved performance and stability. 0.4.1 (2013-12-01) ****************** * An object's ``__marshallable__`` method, if defined, takes precedence over ``__getitem__``. * Generator expressions can be passed to a serializer. * Better support for serializing list-like collections (e.g. ORM querysets). * Other minor bugfixes. 0.4.0 (2013-11-24) ****************** * Add ``additional`` `class Meta` option. * Add ``dateformat`` `class Meta` option. * Support for serializing UUID, date, time, and timedelta objects. * Remove ``Serializer.to_data`` method. Just use ``Serialize.data`` property. * String field defaults to empty string instead of ``None``. * *Backwards-incompatible*: ``isoformat`` and ``rfcformat`` functions moved to utils.py. * *Backwards-incompatible*: Validation functions moved to validate.py. * *Backwards-incompatible*: Remove types.py. * Reorder parameters to ``DateTime`` field (first parameter is dateformat). * Ensure that ``to_json`` returns bytestrings. * Fix bug with including an object property in ``fields`` Meta option. * Fix bug with passing ``None`` to a serializer. 0.3.1 (2013-11-16) ****************** * Fix bug with serializing dictionaries. * Fix error raised when serializing empty list. * Add ``only`` and ``exclude`` parameters to Serializer constructor. * Add ``strict`` parameter and option: causes Serializer to raise an error if invalid data are passed in, rather than storing errors. * Updated Flask + SQLA example in docs. 0.3.0 (2013-11-14) ****************** * Declaring Serializers just got easier. The ``class Meta`` paradigm allows you to specify fields more concisely. Can specify ``fields`` and ``exclude`` options. * Allow date formats to be changed by passing ``format`` parameter to ``DateTime`` field constructor. Can either be ``"rfc"`` (default), ``"iso"``, or a date format string. * More useful error message when declaring fields as classes (instead of an instance, which is the correct usage). * Rename ``MarshallingException`` -> ``MarshallingError``. * Rename ``marshmallow.core`` -> ``marshmallow.serializer``. 0.2.1 (2013-11-12) ****************** * Allow prefixing field names. * Fix storing errors on Nested Serializers. * Python 2.6 support. 0.2.0 (2013-11-11) ****************** * Field-level validation. * Add ``fields.Method``. * Add ``fields.Function``. * Allow binding of extra data to a serialized object by passing the ``extra`` param when initializing a ``Serializer``. * Add ``relative`` parameter to ``fields.Url`` that allows for relative URLs. 0.1.0 (2013-11-10) ****************** * First release. marshmallow-3.26.1/CODE_OF_CONDUCT.md000066400000000000000000000001531475016050200167270ustar00rootroot00000000000000For the marshmallow code of conduct, see https://marshmallow.readthedocs.io/en/latest/code_of_conduct.html marshmallow-3.26.1/CONTRIBUTING.rst000066400000000000000000000127671475016050200166070ustar00rootroot00000000000000Contributing guidelines ======================= So you're interested in contributing to marshmallow or `one of our associated projects `__? That's awesome! We welcome contributions from anyone willing to work in good faith with other contributors and the community (see also our :doc:`code_of_conduct`). Security contact information ---------------------------- To report a security vulnerability, please use the `Tidelift security contact `_. Tidelift will coordinate the fix and disclosure. Questions, feature requests, bug reports, and feedback… ------------------------------------------------------- …should all be reported on the `Github Issue Tracker`_ . .. _`Github Issue Tracker`: https://github.com/marshmallow-code/marshmallow/issues?state=open Ways to contribute ------------------ - Comment on some of marshmallow's `open issues `_ (especially those `labeled "feedback welcome" `_). Share a solution or workaround. Make a suggestion for how a feature can be made better. Opinions are welcome! - Improve `the docs `_. For straightforward edits, click the ReadTheDocs menu button in the bottom-right corner of the page and click "Edit". See the :ref:`Documentation ` section of this page if you want to build the docs locally. - If you think you've found a bug, `open an issue `_. - Contribute an :ref:`example usage ` of marshmallow. - Send a PR for an open issue (especially one `labeled "help wanted" `_). The next section details how to contribute code. Contributing code ----------------- Setting up for local development ++++++++++++++++++++++++++++++++ 1. Fork marshmallow_ on Github. .. code-block:: shell-session $ git clone https://github.com/marshmallow-code/marshmallow.git $ cd marshmallow 2. Install development requirements. **It is highly recommended that you use a virtualenv.** Use the following command to install an editable version of marshmallow along with its development requirements. .. code-block:: shell-session # After activating your virtualenv $ pip install -e '.[dev]' 3. Install the pre-commit hooks, which will format and lint your git staged files. .. code-block:: shell-session # The pre-commit CLI was installed above $ pre-commit install --allow-missing-config Git branch structure ++++++++++++++++++++ marshmallow abides by the following branching model: ``dev`` Current development branch. **New features should branch off here**. ``X.Y-line`` Maintenance branch for release ``X.Y``. **Bug fixes should be sent to the most recent release branch.** A maintainer will forward-port the fix to ``dev``. Note: exceptions may be made for bug fixes that introduce large code changes. **Always make a new branch for your work**, no matter how small. Also, **do not put unrelated changes in the same branch or pull request**. This makes it more difficult to merge your changes. Pull requests ++++++++++++++ 1. Create a new local branch. For a new feature: .. code-block:: shell-session $ git checkout -b name-of-feature dev For a bugfix: .. code-block:: shell-session $ git checkout -b fix-something 3.x-line 2. Commit your changes. Write `good commit messages `_. .. code-block:: shell-session $ git commit -m "Detailed commit message" $ git push origin name-of-feature 3. Before submitting a pull request, check the following: - If the pull request adds functionality, it is tested and the docs are updated. - You've added yourself to ``AUTHORS.rst``. 4. Submit a pull request to ``marshmallow-code:dev`` or the appropriate maintenance branch. The `CI `_ build must be passing before your pull request is merged. Running tests +++++++++++++ To run all tests: .. code-block:: shell-session $ pytest To run formatting and syntax checks: .. code-block:: shell-session $ tox -e lint (Optional) To run tests in all supported Python versions in their own virtual environments (must have each interpreter installed): .. code-block:: shell-session $ tox .. _contributing_documentation: Documentation +++++++++++++ Contributions to the documentation are welcome. Documentation is written in `reStructuredText`_ (rST). A quick rST reference can be found `here `_. Builds are powered by Sphinx_. To build and serve the docs in "watch" mode: .. code-block:: shell-session $ tox -e docs-serve Changes to documentation will automatically trigger a rebuild. .. _contributing_examples: Contributing examples +++++++++++++++++++++ Have a usage example you'd like to share? A custom `Field ` that others might find useful? Feel free to add it to the `examples `_ directory and send a pull request. .. _Sphinx: https://www.sphinx-doc.org/ .. _`reStructuredText`: https://docutils.sourceforge.io/rst.html .. _marshmallow: https://github.com/marshmallow-code/marshmallow marshmallow-3.26.1/LICENSE000066400000000000000000000020501475016050200151330ustar00rootroot00000000000000Copyright Steven Loria and contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. marshmallow-3.26.1/NOTICE000066400000000000000000000031371475016050200150410ustar00rootroot00000000000000marshmallow includes code adapted from Django. Django License ============== Copyright (c) Django Software Foundation and individual contributors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of Django nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. marshmallow-3.26.1/README.rst000066400000000000000000000124401475016050200156210ustar00rootroot00000000000000******************************************** marshmallow: simplified object serialization ******************************************** |pypi| |build-status| |pre-commit| |docs| .. |pypi| image:: https://badgen.net/pypi/v/marshmallow :target: https://pypi.org/project/marshmallow/ :alt: Latest version .. |build-status| image:: https://github.com/marshmallow-code/marshmallow/actions/workflows/build-release.yml/badge.svg :target: https://github.com/marshmallow-code/marshmallow/actions/workflows/build-release.yml :alt: Build status .. |pre-commit| image:: https://results.pre-commit.ci/badge/github/marshmallow-code/marshmallow/dev.svg :target: https://results.pre-commit.ci/latest/github/marshmallow-code/marshmallow/dev :alt: pre-commit.ci status .. |docs| image:: https://readthedocs.org/projects/marshmallow/badge/ :target: https://marshmallow.readthedocs.io/ :alt: Documentation .. start elevator-pitch **marshmallow** is an ORM/ODM/framework-agnostic library for converting complex datatypes, such as objects, to and from native Python datatypes. .. code-block:: python from datetime import date from pprint import pprint from marshmallow import Schema, fields class ArtistSchema(Schema): name = fields.Str() class AlbumSchema(Schema): title = fields.Str() release_date = fields.Date() artist = fields.Nested(ArtistSchema()) bowie = dict(name="David Bowie") album = dict(artist=bowie, title="Hunky Dory", release_date=date(1971, 12, 17)) schema = AlbumSchema() result = schema.dump(album) pprint(result, indent=2) # { 'artist': {'name': 'David Bowie'}, # 'release_date': '1971-12-17', # 'title': 'Hunky Dory'} In short, marshmallow schemas can be used to: - **Validate** input data. - **Deserialize** input data to app-level objects. - **Serialize** app-level objects to primitive Python types. The serialized objects can then be rendered to standard formats such as JSON for use in an HTTP API. Get it now ========== .. code-block:: shell-session $ pip install -U marshmallow .. end elevator-pitch Documentation ============= Full documentation is available at https://marshmallow.readthedocs.io/ . Ecosystem ========= A list of marshmallow-related libraries can be found at the GitHub wiki here: https://github.com/marshmallow-code/marshmallow/wiki/Ecosystem Credits ======= Contributors ------------ This project exists thanks to all the people who contribute. **You're highly encouraged to participate in marshmallow's development.** Check out the `Contributing Guidelines `_ to see how you can help. Thank you to all who have already contributed to marshmallow! .. image:: https://opencollective.com/marshmallow/contributors.svg?width=890&button=false :target: https://marshmallow.readthedocs.io/en/latest/authors.html :alt: Contributors Backers ------- If you find marshmallow useful, please consider supporting the team with a donation. Your donation helps move marshmallow forward. Thank you to all our backers! [`Become a backer`_] .. _`Become a backer`: https://opencollective.com/marshmallow#backer .. image:: https://opencollective.com/marshmallow/backers.svg?width=890 :target: https://opencollective.com/marshmallow#backers :alt: Backers Sponsors -------- .. start sponsors marshmallow is sponsored by `Route4Me `_. .. image:: https://github.com/user-attachments/assets/018c2e23-032e-4a11-98da-8b6dc25b9054 :target: https://route4me.com :alt: Routing Planner Support this project by becoming a sponsor (or ask your company to support this project by becoming a sponsor). Your logo will be displayed here with a link to your website. [`Become a sponsor`_] .. _`Become a sponsor`: https://opencollective.com/marshmallow#sponsor .. end sponsors Professional Support ==================== Professionally-supported marshmallow is now available through the `Tidelift Subscription `_. Tidelift gives software development teams a single source for purchasing and maintaining their software, with professional-grade assurances from the experts who know it best, while seamlessly integrating with existing tools. [`Get professional support`_] .. _`Get professional support`: https://tidelift.com/subscription/pkg/pypi-marshmallow?utm_source=marshmallow&utm_medium=referral&utm_campaign=github .. image:: https://user-images.githubusercontent.com/2379650/45126032-50b69880-b13f-11e8-9c2c-abd16c433495.png :target: https://tidelift.com/subscription/pkg/pypi-marshmallow?utm_source=pypi-marshmallow&utm_medium=readme :alt: Get supported marshmallow with Tidelift Project Links ============= - Docs: https://marshmallow.readthedocs.io/ - Changelog: https://marshmallow.readthedocs.io/en/latest/changelog.html - Contributing Guidelines: https://marshmallow.readthedocs.io/en/latest/contributing.html - PyPI: https://pypi.org/project/marshmallow/ - Issues: https://github.com/marshmallow-code/marshmallow/issues - Donate: https://opencollective.com/marshmallow License ======= MIT licensed. See the bundled `LICENSE `_ file for more details. marshmallow-3.26.1/RELEASING.md000066400000000000000000000004231475016050200157630ustar00rootroot00000000000000# Releasing 1. Bump version in `pyproject.toml` and update the changelog with today's date. 2. Commit: `git commit -m "Bump version and update changelog"` 3. Tag the commit: `git tag x.y.z` 4. Push: `git push --tags origin dev`. CI will take care of the PyPI release. marshmallow-3.26.1/SECURITY.md000066400000000000000000000003001475016050200157130ustar00rootroot00000000000000# Security Contact Information To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security). Tidelift will coordinate the fix and disclosure. marshmallow-3.26.1/docs/000077500000000000000000000000001475016050200150615ustar00rootroot00000000000000marshmallow-3.26.1/docs/.gitignore000066400000000000000000000000431475016050200170460ustar00rootroot00000000000000marshmallow.docset marshmallow.tgz marshmallow-3.26.1/docs/_static/000077500000000000000000000000001475016050200165075ustar00rootroot00000000000000marshmallow-3.26.1/docs/_static/apple-touch-icon.png000066400000000000000000000200201475016050200223560ustar00rootroot00000000000000PNG  IHDR=2IDATx^4YQŜ*"$L變(̤]PPDEɈEDX[PA@Q0 Ŝw鯿 =}u=μ0ӷuVսE9 54Jrt`QH@/J9tb`QH@/J9tb`QH@/J9tb`QH@/J9tb`QH@/J9tb`QH@/J9tb`QH@/J9xȻ[Cz z~}J/UKлEПM_eп~UH@_tc;}`+@~렷m6#fAor/ 4C?L-86@/M 'A UߡY[; ܯzIЫi:w/^AL Z |$О̊ o& {|MӠ? sTc}\G4};JxA,(?{ <4iU]5!Z0ydx^w mhsZ^{A@okϻ2m348>(Aw @^ ǖe@n5 Uu<}VZ5͜~2^7C[0v0SAF 4imچ^؟3W&{6~.Zo4?Mo ޵ֱyzs4D^tǠ4BuusaG}~&eALTsV׼Y7 bf}LV\͝tUг~9䡟* 8t[q>Mލ>P|An3`-r qaexKhȔ`4(#*IAwEZA ,8ʓ<4b? sf6eK CXY!F9}C&lmr #WajwDcHЋ&>>& :Fq8- ;ƌ  '/rO 6dY( z\#I(6{͓Ocغ^iPn̡ aP$N+m@odvQ}F}nX=r5 ,lTvVPsv&Cܠ;63C$,J/: aNmUa;V0>wEb#Gſ uYu6Al1c"˹9-Kb៟ q}/ {jX}hKMI>6u6Y,C.3O hsQ?!A ڗS5ǚWOes$S=:HN=%+Ǯw4{Id|̉ Jml&C6DK~6Kvœˊ zZ{ )65ߦd2Zbʶ4- y5N$/>)\m(o^}ВD*lg7OɁ6q H%]nAvyl"Ɂ68 9wUw)@Q_7ҟdH@WcQ_,x`׸`L.LX趱_³3ÖW6ff<$AU4/ۃ$?5;2sثrӔ@K3H: jIW5#9]@?*f294< m"~s cR{U57pf_@Â^Rs&EۇYn^SwTSCdC$ELulZr"([lW9^&;c@?1,Q=4+f_ )hj.mq':=Y,1Or_ ;R|!NAn=]&K>z3-9(?ܗbREeWjICÓӃ&kC̒=4Hn–%Z(9u3ˢ#Ȳ(5uk V_L@vڹ(lIM=Oäv_܀m{mixN媇A k3&k}M=Vz@|GbкNC[:nJ,@`p ɧVl R5 YO+@;]lLD&.BXfӏh PlZGHC1ImQ]C2"$ƒ @Ms ǎR!ߪI#q4孅A &mw'6*{wu: R}aza"IRWKȔD) ؎L1CwUZpf4r~TBwﯘ x#bҎsv,P9s;吓)7Ń ?su9̀u3{<Ξ*Cvz%lh\] u;.@> ܛH- aV88{ O%fr1tSUJ |0o >7L9XɆhh v0Sf9PNh;]szF޹C 0j}=\rVQ__>& !^-AB? bɩo4.x6Ap2lErѐ4%b+y{Weʰۮ߻+H6/K 8cPwͩSE'\=F#NĪIlC-_U;^l1?h+̗^yv N:!RK{[X6"Cc;kƩd14z]<6ar؅=:C*cwg>AA>C 3|v$c@ թ֭H?B^#'/jjEXiJydsBºqFsZ\(yjEjc%mu~w&(SZ:#O/!k!2N>Z_>uBBf>V9ϻ@ %$Hy:p ` Q{Ũo1yԆNTUgXY. b$nR,<48/2KSof9Ġ27FJ#IUNu׈BbM oOzЈ׾RCɖ>$vC1siG DD4(|u%|.A9чSɣdn0WGNu3;TJu6ܻ 8.~nŬ]AV/)fX@TґTXzuT˸K@,J8T7Ej+ D!8 2QO Z9pE~聺J@;DΉTsp bgθ01i헫>1B',sfYn E>У4/lg U{ ỡCo^h-% zbmLzN-25?2q耷˃.8>{ {Q#9c:jz l݉NX-8ZIq^c:3ݥ]*"kۧ (Gq м@nݯQz,@)yB?8,زO.;簷oSݤ{:'ucvg1m>`0a84ȁxq^ڱn >'!&jzPI9s@uQvH QzIޭ{Ar\u ϑպ}ecNNL+x% k $W FZmnˡ<jq]JI2~AFUmv]CJ5zS őw8o (&[JtUi E{*m :h~;ţV }&:tp GyhV tM''IXM:̳}%7ۚk.PcZ DY SdXhj0߭T:_˩BYQ; pxb3jz2 4qrgUxS([H: mP,^{NVhB0yuhx\RSS40Ɍf'nAr/+`'tqkل3)w/ 4@kW4MB3ǜqc*f8ٲӜN N@%^}e@+=Wcm>4p2>]h/37]OZ™+5 ~C 0$zye˲w؀h ^b ) { H3# JFc !d" 4s)~ѽ : Q 3NDЪ47<A= ~xUABŽ$}(Pڒ 9-crT$H&p UlE&躜ډ퍷KM6 [y疻c6rL(Y6vm`3$~։Ln؊)ً$0'{z<]C3]l @o׫yPCP=[ "U]H ҆Ҡ 2Ek# էcN`73`lqoV'D bQ~bv8KVe,@?<./2#7גL"%igXW/$Yɜ/cZBB-J3Ҳr3Y;A fM >G{x4^x0eAm<}NITMof |TcZ 0雎Npn$G^:DVnP1I[$|&&!HiZٺ~YV^Cg7l͊/@l7HAjݥJl76gv$)Sl#(|b37 Ñ?il,0= zm_**<b=Ḵ\01xqn.ى0ptEAc.n@ 0Ibvxb=jN-, TYծQ&1n=kٟր3`H{ysiHoBD26pL@u˛Ri;JZQ4yí3!8 Im`b3Ky7r>& (=#LNݛIoYUrIT1y1C)09ێ$9Co.a>!HI 3]4S&'b8!3>mԠR,< u[dv˴+s1J(*Y+ЋPh__Hw+2C!M*W&!A\AB:'z`:˘M@8}\˽GC6xQ6~ygo 6$xQHfk>UA4p =F:q3%: U ]?Hb]]kG^E,4P:tam zQ(=eH.U֒4+ #k%ypCV W$SO nl1`φt[60c8 :ek ?-Wv*4:[A/k~_Bh* yrŲoͮk0b1WV˧pU vh5; Q+KgpN0ʙ)NW5e i/b G=(};hG*Ӭm4.g=\ؽ̽}Ii:0_JCocP/0e.h)0tm EsZZ ^fE{0 0ݧ%4P0Oؘ07fVt[&ucL&ngynD.&K{1黬 4|A&{W vf=g1pVLf r)=C2֖tusU6]KrjODZ8L#.~@Ya]VIņk1vuM2l3&-qWӀ\!l(gCҢ,5`9a]mw$:$f]K.<%@\xVU eKoz>%LKnz>%LKnz>%LKnz>%LKnz>%LKnz>%LKnz>%LKnz>%LKnz>%LKnz>%LKnz>%LKnz>%LKTq讀TIENDB`marshmallow-3.26.1/docs/_static/custom.css000066400000000000000000000012471475016050200205370ustar00rootroot00000000000000/* Headings */ h2, h3, h4, h5, h6 { font-weight: 400; } /* UI elements: left and right sidebars, "Back to top" button, admonitions, Copy button */ .sidebar-drawer, .toc-drawer, .back-to-top, .admonition, .copybtn { /* Sans-serif system font stack */ font-family: -apple-system, BlinkMacSystemFont, avenir next, avenir, segoe ui, helvetica neue, helvetica, Cantarell, Ubuntu, roboto, noto, arial, sans-serif; } /* Hide ToC caption text within the main body (but leave them in the side-bar). */ /* https://github.com/hynek/structlog/blob/b488a8bf589a01aabc41e3bf8df81a9848cd426c/docs/_static/custom.css#L17-L20 */ #furo-main-content span.caption-text { display: none; } marshmallow-3.26.1/docs/_static/favicon.ico000066400000000000000000000360561475016050200206420ustar00rootroot00000000000000 h6  (00 h&(  (TWOKJNNPB@= MLE =Z<)< #6"=E!?/:<?L5A68=UZOQPIPP9V] XjOQ[XL #OV&QXTSN.( @ ?`q}ue\A&"tG% 0IU|t!1; /0 j>xk%e<,$V0V6.s68AT@7L1P.zT,a/q_=Sko jzkz is $Vb|sfW3[dVgI%",*@``8-Z3C[U(*X(BkZ];fD Q(l}0)f] CyA (0` "'+17?A8*&$"  *Fcb:^2  )/IW %Nb['t BS1sm 5zq6&?_v_D4o'cUUKx" p}G $,SS ~Fdq vu;|9~}#/kC_z $SFw%S Cf _5ye-~h+m)p&"%.@EBA;70*'$r"u9js@ q"[wN(  $QQz% 9=:u<#}=kE K"A$^3 {!sjA< @ CwP% U` <i$ 8zN ,uh]Z^o[&Iirz|hQ1marshmallow-3.26.1/docs/_static/marshmallow-logo-200.png000066400000000000000000000152311475016050200230020ustar00rootroot00000000000000PNG  IHDRQf cHRMz&u0`:pQ<bKGD̿tIME +ՋLzTXtRaw profile type xmp8TAr0>AsXu>Վ5q Xv'cYIn^db'-)ͪKl-ƖeZfN{sNTlq8r*}Xj8B &SfUR;ேCqJ2spuqm΋ 2$ c i{9x嘑er1بP"ûta`CB<//t^j9Mn~6Ԇ&ra2x A4Q>ԎUn E)Gh"TAnxNs *@-JWNQ!,Q ]ցlzU-jAU*id<[ŵ< voIcp&ujgqt9>Yݣg+>48oԐ6GtorNTϢwIDATxyt}te[6A `6$)'9aJҜiҞΙ4mӞdfkP2df C-ٲdt].{uGW{{~ۿ$D)%Ju \ eǮrPFey +N:.z e:3e J]hZ5rATrKr;Yc:쀓ug:s0(W&mtŞ:iv>V3Ȭ yVХʲqMT05irLgȔ um&Im5SvҰ' '|3[|UJ)SB5q-3Yj7۪n0jPFv't2װQ0Ie!KRe-ssrə ir؋vx>^* Y/Jw\nͤCa}.ӢȬSVRXm:V*'goGSX6qC|+\l4X>[ڥnm&' *U9;OVov뜇GrdUl"7vu2#k 5إW{3x{Y˄*0$J\kTLa<O8g~2\24Иu9hg<`\ Z[l9*'w#tҠZ[])NdDv n37:\^vc{;] 9\|5>I? mrm.lR!3^Y'FW&+'ϳ!G:Yfp'T1: bP a^?[Q$.D>Wω9E,"V{wֺqeċ#2Xn}͢8]M:Z*D1?s2!O kkq3!n!&ԋAO߂ED>Z]LByHqIzme9~ǑqsHNG=2ˍvjk{ҽ}Wj h{ޫ ܦqzv bqPm@otᢡcG~#e }˨n &M ~rsP3OHvם_3I)QQ?UT—kr'OIk^`ߗ]??R q"!ǝ~u:f(MVu RgM|uL'5zE4+M>+11>8aG x_߮Fa}vfupYo-> Gׄ&r=<;A䥼2sʿz*Mouָ |X&uT&M:u͐J,s]Q8vk\F<""e3@#F0,#:t^/ۭU/ pι c_*j}Q@DleSXV[J"2:i Ghh7CNmTaD0GG@ng;OSP5Ǐ;E+W,[\&BԠQd(|=Vڤևdd2׫WKҝ ݺ4W$w 5.J&j7{3a1DJ&Hg:IjhANfRagd@t`&#.2:  -dI^h GbcҝNsM~X k xctYJa,Lw5+ 鰓EA6Y#a>CfLޅ{Z%Av î?uFBav@UrDn/Z I{w2VC*e-YeΈ~xt]*?v\jPR>Չ^Bjw}YXa.|3`Wn<ԓOjO:CڐXQE\\: ("9 q?5 LQ=N3 lA5f[H=,󷮊_E|@& 2 A&I\D 3z}ctꕛnSeT8+7LGKu% ,R%\xrӠ4W21Aj+T<&:AJ~пP<𗠃ߞ҃;I.e~Y/kL sV'DK @ACz?27K9.SQAUR1LVXbNΏq#*q=C Cy$N$N,nA_c M VyMOurfn X8+$9+%tf3vT3O.H_h>c4,n=y[F9'ᯅ[Wu;sFYɈ#Ǩ<ʴDkzyF 1,/5;r\2\OTkH$̚MzU~V?)UA / Iϸw|mT!91ML6ē A49?O*T{a13:FM>u0ؽ!oINߚkȀ~d3p}E·'M F^# LJS"!;tvϹ}W)A#TuEǽX>ٻOֻ:m,KfOm%{sT=nWC'}۞ ~Rj4~l/nj묱JsS+RdE_>fPΔ-1=j} DYd?JMS&ȐWsuʕ)S("g-+#gL)_}׍1R:W1=9JbǥVXVUX99O;U;rգ0V2lҠrW{v þ/ԯvjxrѨNxOXVo6$ngK0>+Y44: dkfduz-XSq -Eè]Ԁd,k3<>O/'ݎs񏢢͆e.HRX;so{n $Rco>pObo#2N5bj'a8p!lxYJ/"P2XO/`x2KT s_LzRZrqKXBM&hYr!e`qojs||9&b$6%(rk1`Ss"ݜYdY#wr{9fLKF(w24N%3ƹ3'2HzwzTG.wa&g.Ra綩X<nt׌b>=Y]6>idXl\56µ.N؇\'Jf;fl@N/ TV[|I`"4\w)>t«^v9-\d+6TL(>vN|ifޭnq~q>4j9vQ@u.q+dj b'U6f󬘶k֯SF_6m~4xkltZ1ˏ Zp^OyN'tWZ`e*ULc`4bPݎ:Ԝ*WD񸳳rՠ)<^CiŲz{1GZ˭P<ԩS4M*h考kQ=1'6LfʒPb *PDZVBQ;4_{ a%K` ֩?mzQ/KTw\Qr2^JTRDBɒ|hĀaS+Xu\d5+gjծSGsTfִ0\U6 ȑB2QâFg԰,C:tkiҡ[O̳iɠKJ)SJ*y\<+F܄ѫq͎kjD{]1:+f\YDd7Q0:֡OneXIfMM*12iDAdobe ImageReadyL6)%tEXtdate:create2025-01-10T19:26:25+00:00H%tEXtdate:modify2025-01-10T19:25:31+00:00+(tEXtdate:timestamp2025-01-10T20:31:43+00:00tEXtexif:ColorSpace1ItEXtexif:ExifOffset68JftEXtexif:PixelXDimension422(tEXtexif:PixelYDimension422`^tEXtexif:SoftwareAdobe ImageReadytEXttiff:Orientation1;tEXtxmp:ColorSpace1 tEXtxmp:CreatorToolAdobe ImageReady*OtEXtxmp:PixelXDimension549F؜tEXtxmp:PixelYDimension511'EIENDB`marshmallow-3.26.1/docs/_static/marshmallow-logo-with-title-for-dark-theme.png000066400000000000000000000265371475016050200274110ustar00rootroot00000000000000PNG  IHDR%")e cHRMz&u0`:pQ<bKGD̿tIME -M=s,IDATxݻrؖ7?NP@chܘ*9>L92+;2;ӄV2<ĭr=5 'X_~r5EA6@DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD4Āp'8Xb  -].ڑS1 &m08L XVu"b*!1`RġbS1p`9Z(>V `bkB~#zieF]ٵ8)M >uhHs`ߘJ)];y@`;ޅDXaR y*ngxw8] SɑD~"ы``=A .|lY:W-$Hs`jc+\p'񷐊`*911 7 -&1tSIĂXy?b„c2?b9Yjo!m1Dpyؤ_{ .4X^Ģ{u1b*iX#?cCqX,5m?bJ"pj)LsdR(L(ZJjS8ΫlH sssn'Xp_SIeba ָ 6f Xb w;-L%]VO,y,J,8VJp~NSd)+'đ+,e&vZI.17OXEMB'pyTK-4M,L1%zӘJ4B^%ςѻQL%;blRHa?{dimJܚͩ""Ȕ{zĒy٥8ڱS!SYHP3ĕ9 41dV`\cĐ,Jv2ӎz$+M' 婙DBzLL*ъtB*] sBTBa:01rê/7l1 _0Y.:ʢ юSi/s^ʌ/xQk]ecV%2Nb?rA"Nf%[FVvԤtJ1SKJB1bȬԅB;bj\4$B, +"lCiI `d)5DCV`2dV`(%5@ J#.QNTK&19c% 5m71cJ %@bhK'WǙϣpiJ!B;>*IfL#C^BG;>*ABm#V=<׎J(0y&0jS MUp3d(`%'e.mw\LTr!>LjwQa2ML$'r1X)8饃Q3h" $-V!ɂO%M 1Q$to\;(mL%DMx6VtS QYv]TBTVރvPD3$XQivU SbmfTe:ZL;",4wɊqSvZ"VcZR &fSJOǑl9f!߯c3t'N2mR=҈'U&+YLKL#"od%"P p T"̵6M{Q'TD4""r3UI%OFz7]Jr: Fv4h*"ȂwPT4TorO%J+Y H舙LKjG;~ls"YB'ZC‶1QG*f,q*ۤ77N]gS{ĐYō' 1J"=*ND[FV2sʲS %ҍh+c VF/"-*W,eL% -#" UTXŋBBYɜm'S"[2٠BBqe֓(FыgU,ŔR=<+Yɋ]J6H}!f8ci8H%vReu+bȬmL'Vxte*M5Rm'QpYœƦܰ("VDm=2/ Wnd.Đ2-V54k"Ubl0hxAyI TO[xC@ j|edu΂@,*5btUK`M{7 ͳmC˲}V 1 \ qT)yȕ,*g*PWgU'u>3*' T56( &fZ+y&170 X?1- ycz ŻƁfL(a| jk;M,ʸW`T]I%q\Y@OM,㩹&L`FVdjL%&>|@ 8p4to쓔뚋7ԲKXx5UGi6խb1`^#@0S~D›% X,5[= !dbnzkI+IWpᲹ J:d7@"`ei#>|GmL%-)ZhfkQ6\Y´Ǿ6(o}A_S*ϧ~/V4S5/LYfž my{_=5F͸0C;6WnR m(oMk{[úP!7d680a‚jk(!&a%ed#|#J5,ؠ.D*g1Y.ʫVtͿi@G-v4]TBT[a*! #]_SF(t'67XXk*rra"RTBTzhvTBD `*!$rߗ+S?5H[_S xWS;}L%D w&†Jg"uCH%v4Rv]2TB#ؿS;mL%DM0TBD `*!0Uj%L%D"jS Q1QJ~j@qa}M%vDv]TBDTBD `*!5 0Tk@NTBDTBD k*Y^J|( +K;mL%DWT_0587؍TvXjG<'6l1qu!3E_ʹc=VS5}6{H%=& ~OT; cN,[bj@,\k֎VH&ߢ%ڱo$>1_14nb$v-ȿk#K;]I%}lxh69- f*3]I;IJ&vGڿM%ҎYndiS;MM%?'v<XA@S; t= C`"K;ML%z}iβ2v B `Ȱ'W;J[HƵi6bl'nv ooϏB@*IM&AIvd2_;$TM%L#bhGUs%o]jBDXW;Z[2ۥnUϨ-ukǡ U=iGS{kl(g IlG;`XdXĈ\~Hp*ҎFP:S&+8xҎ5~赡jć_|w*FE-07oA赩 U/p_u0^ha*!+TBTWzmi F S Q]APTBTWa}8}N%+ 6q>_;Ȓ69uEzmi>?FS Q}0(҇#8C3oU'+kQRŋc:Dx# 2v S uGf)K,㯏m%Dm%4e C;JfTnG}4bM|hV9w]h_pj%v4j~}ip8HJo;ϩdVzŻ}N%vD!t]_}N%D]Ki_S Qĩ_/a*!j¼>adZ!-)1#w18Xx^y7[2ZL%h$Dnb*!}b4HD2:6c5B{w*yv!j7ߩHG|e*!e?_opLJ\u_9L%D5MVֹ/);u$R2jo[QL%D -!.fTBԈna7ߩ($he˕Tfi@g+ +b8%'g8hc*!>G;|L%DMq[[/bjo\>03T2pTh@kߩC&+415zc@EvzJ\"|; )N'FNjmͮR*d*>l hoڠf4׎CJv"V[io1X$}i]7i),+_/pov0T᷄__;Z~V_{M;VPybaS~Ў 1AjoVJkS;',&7/YJ6 1dd.diGYS:aH~/pbNj+\ H;+WS mMbbi42Ak{ R{y*6iL1}KOX;qE /v55ωúD9͵i| it{!W#GVvM2ޚQw}ӕyLMl1FV=< 8(\(^iM[gTBf[+KSaEj\#\ /וnB,,q+T-#ՎV<}}+'1+E4}&_-zWNnnhP;q~NLhZؾ>^ozMPc.,[߾9ygNX"@0Yho+ڱ}%ŬdmK\tm\ V\1$l &_pv{$LuᣱooǕ!%p0\ɿHJV׆v0(&{pndjPGoI0YcIXH[o}Bv٫0߾  X֯/^1(p\n@;:JVtǦh~O,0ab=1ۮL\y-M%\sK D.đ+[2vlڻ;WrG[I%v4}#f{Ie +Ѣ{[NfQҁؽv<$LeQjG/F)QByϰ%7 e;ӎREnd$G;6mbfoSLn@;S2neG;*m717GBlS;1c2#Q iJVXv(VhGEC%Vl$ 訆S;."fJ:\M,r4BdV*9ZSYJ)3[l32')rݰEĒ, }AMeNWL qd.ˊuOMhpLbb?X+\pOV4Fb+y~bIC3T4N~‡Ͷu yWyXb9Yjo0.a^p[8$bVy1ndM,T$yB>VW{;G,,jA֞b޾517vR Db&P;Md .2T9ĭ\0fL%9d}n„_ZuJOzv/S/b>c*)Ag&<`"dBZ1kB{L%F9YWY{ F]\9>Mх'2~5QJj;bR)5dM[FЦhW=޷0[~,-=6L%C@ݷ0 ׫+7.HR1QJL%D"jS 5TBD `*!0QJL%DԀV>.BŜ}[/<.B3t\پ).vQm} ^BƦ^o߻.͊;9N7~[N'ix*mNc6vyȖ㽷q;fKSeLc|of})6gu]Sz.uϦ)gV/W>&n평鋶qͽsy#4!OBk/:]ʯ辫td/bsևQ3T>| /S&8'|O9 w/rxgw|M9|N y_3gwVpIq}烯𥗐o:]qz;~$D]7s_Q~MνH|_ŏ}'xNT Eߑ}h|.Rމ'\w'>6)W=3x/ v5*]|>9|5J8$ke>x-}Y9~8iGfMΟ9M4TF=3Tpx?oK|ξ "1z=>?;~Jp&U?3σ{oӯԻVM_־kl^J.Bx2>X[kQKv?pE=ʶ8X+ptq_7C UgާaxEҎ;} B}l/}g_C9x[}\^ h?<Gm.xJkA"y~cE N-7KjLy]o"e~_ |gs 7Ń&1~5qhjΣR/N8 Mfw&|Ʒ]CrC< D>˫u(6k%^Cz#wp[ 'JbSAdo(Vbf=)k;aw_'7~ZAV>ZU?Iz~Ie}ۺ6!|x/-w[Y %q]d3%B'T=~NK,i.tv9o?j1]CUg_'Sɹ~98pϊ,k?k'pK&s kΩmطgZcڢ1N S~^>9HRc{!qY?<$o_r!i%iE{Gk擟íC$\ǜGDj1ʯ^ș>^i%Ny [JQPcB*),Y.oS'XޞMeJq}V˦UJWA4>\]ďzE¾8γv Zn ֌zG;̽֡-Y='E솵U+Tix]1ko?Ruv*OP)K?֠Vo;_ o/.{柽3\aօKK!u-Z=k_HK̙wrؾ]l[D[.넥}=RIj%#﫧yI{M%9يr.~ $K6TSxw}IqԅG&~2(O9)Kck<8xۄq/FR*YWsj+) NߙP:vDqpDU ִvhr8?H9zʶ5tl\0 '[<3:|o*D֯xiM/}g]O2u'`k-eld-񲹳a{34w30wNs,Jg1rD]>Dj/c256Lpu o3d[;g1m_SQ~jb2^u#<.JNPɳMª ;>yoXgxNG-3|. nV~*2uq0l#K%މErh[1t8Jmg926^PCe+U*#R)Ga1}mO1pGIwtv6&/d֋Q]:qtw^ޛF 6iGj >v?y'^dRV?>q0d۞&FY&x qt}mOU|Y_!-HyJ Vd;iM M* ]zɽ_]|cXߟ1^q:n5itG$x9_OLwKT4Tr0/JwKjY~_䢷?ι_%ۙВ3b|3=T+^c/O*нk7U=ֵpЊQ >H]!ZU'9k&zo񤒎]b=2L%+K"']E:}9SC5kXRv7i89Rw/}14Mt"sIi#6s0R1cI%Ghf=~*9l5_踠 MD{y lQO%e :|T|U1RݭuϗP ٷ4^F W~U]ھ%阢Rx"xΩ\1RIt?yn0ShR^|Km]qNa/%V1rYAq̻qbK%uxsv1y?yz}pyg8ey ) ?;%B>qRKo$6՗Fl.G(?v{0IX~w,JllnG#;;Wv}[xvKC?67z-ɱA{R${ScҵlgK-_%2v}G@rZXzp`=8frAןpg 5Н3{B_ΏM?sW4O%9rP'I%lImzGl?%+g}5~_mcW<{ >f~=>f>6{wR[iktw-%?aߨpd8Txq2>-B#2r f8"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""t""%tEXtdate:create2025-01-11T02:22:26+00:00 H%tEXtdate:modify2025-01-11T00:18:44+00:00j8F(tEXtdate:timestamp2025-01-11T02:22:45+00:00u/1tEXtSoftwareAdobe ImageReadyqe<IENDB`marshmallow-3.26.1/docs/_static/marshmallow-logo-with-title.png000066400000000000000000000331241475016050200245740ustar00rootroot00000000000000PNG  IHDR% ?tEXtSoftwareAdobe ImageReadyqe<5IDATxݿƢ`)_XW7™DơIN!0UrS,`g ٖW 3#iFGWZ?u׭Vk4ڎP%B %P B P%B %P B P%B %P B P%B %P B P B P%B %P x+nox1YZgs(ݦ,nG]{6&qKb>wB˅^b㸅`s>BB t$2 }YBR4KFVB PBvBex` + \ȂQJGUBHY:IJ`ciRm)FU@(ir>+|l7ܝA[, @!d~J'5] "ad/<_:Lۜ3׷ܺM8ȱwh+KvIjA (+b.ѐw1K\~)ӤW1<PJ^kDꭚȲs|6ocn9>p\"@s,tN lI)g1<C(5CHj|b^'M7[ sPKIἐ.vDLv/IfQcB c!ӤɩyDACCO޾RH,q!uc#^9UarJB.|'V(mc7B j(/΅>,l=e#(m!ưVgSkl2OX%J !mʖ?AwQ$iq aɾzPBA$yu,q֩hn71(u)!\x# B 7T{!XC{pPB)L@Su`E8A(ais16:!9Y5; q0uXj'>hdFEy ܮrOd 46y/ lW[;n%F(Q{I(x(}s>Z0%C8X"eڸs/9a}P"{2.Kڠl5q״S% #n4w9(% #Pw89H9e~UXy$ v%O}SS%J_J}1BeRxߦbN+M 0j] ύFJZ#l~ lTd@ld&B(iޫ)䅹"@Oڹ02Ֆ!4_QC5Ϛ݌u:`nF(i 0 %u"t{0&6T1uM9#?ۮ@]`5~|Jn:F]kL|X-!ipBٙ%/.*aL` NaedeuBIBI=^=>Q8Jܑ=OF!ϜfT @ع|T'ŷŹ7[8 ߷FJ3RҜ[H𼯃I?2ty%`J*3!p[0nb?BIq/9%89nTE+\7Ph2_VK͏9%4bWTB uE @L`E(mP@/J(>{jvl$ꊰ}adKiBcuV|yA(V89#''gj5B8900'NF &λCna' h}w0Fq.ˤA/,gǬe!xG>o7uC/?{Rea$ojocrҢ#tXJ:(g'gkc7?lFr/PP2P2&1E.7+BYlݵ+aQa$]ۯsd4-?Fa1dۊŻwL;vswfcH75_c@k9$L)i9ͅ~vy'\mW; #uk3d=cMKJ^^$:'Ǿ R>Kcv'w%ָ K BI۽ 7qU e*Vi{ %%hlU~;m ce?PAHؚX[7υ opr3if퓥%9EXCnBBHBCw­G(A(i2s=}niN&ޟe:"^ߖ/ 2'O 6/|P~g5ltT# ԅpG}?Tpߦg>4xNU>o>&>j !7sABG/%}P~e 'EKAo3b7l]nO[a/1>wG/y ".k>Q!<Fbr~ g]_иO,Qhm¼i[6s)iq%#))(I 'a$--q{]⏭\wnFpt9cSzXXtyw@(G(Z% Qv'^2ȱQJwp_MΟsAPydM^U4prˍl>/6b> z<1~i3v{/).#%z|lZ}EԾ4hK?|]o9 D%guT?Yќ/~XǾ> -Jz"섷nGvNd%lOɵHfk'w '/IZ5DJJIc,P7 oȉJ辡5lَ9,ְ%B Fw:yJ;״4~!8mc O'#[B Bhla.ɾ"\()^Z%0!ƏK=c^֦#[LFU &aisGJu"w >gD(JH7K]煞@4K-yw:ts{n.'CK6['X{7P*J螇uNb82R&̒j~O\nْ %#"N~3%KxW-CaKQm]uq|C 24Y='˥Y6eP`rɢneArh`d.׳8P:tE3>;iW mN]_(!`dmfIXP:^JHPB B BIfhC(`>zJ܁#KaD+Qkm\q[ǁ1UWW:B E(uG++}d ,_bBGW(JLxX^2G ,0rqY'KG dl?bqEXq8==J΅pG%L%×!%loP2ϗ|~hzvLܕ#:Sܽ]B [4< 6Lmu޻n:B/ݙ#%[* %"%lo0 MPvdXw c"䧁\JL,tQ2Z턑0q||ȁqٵksFJyO Fܑ,^H"݆0R"lmh`0 ve>PRMO+\X7LnFv]2%ײ/OCC=)ƭ6ט5rre?nFyǭ:zJEP0N7plpmA@}YBUY({IxRJ.L@lT[(ٶJ' x1 la1.|6kرbir;=`8!D"DKrْpwL[zBv|>ͣl8QɋBuզڄ)n0ۂ/=φu8w1fWf5wݻ a$9ryI 8B]O#u,.Bm8¥wG(JܗBօ!2gm8!HM|]~)ar=҅mx߿pۆ;>GU9N>xTt"oVݺ0 ^(ɂIcp?%cxNj*V(A=bF^X67ۗ0&9(,n~yEBh+^h$ %~;$WJoVm} Ɩ;k~8ZTB/S#: u(K;,.@"ٲF*Y-NbPO<a=5LaC3E @w%8FT~Hf?W2k/Ӳ #pP#1R*TbE8 '.dIg dM;NJ^(3=;|X]zH.LL[ZmPѷuFq-{ؠöhvs\8e#X J!{ %_+zZ%c!<˝>Un3:2\.ǪޠгMir,l@?" 7RқJ8I?gɗ - RjK˻F(ʸͰ^ mARrM;L %JV. 0/%H mM9%o $ܥn{J آj 5vLK~_mN<r Z.!C\ϨPB*~'&vC $#6KMh +->.)Ms06/t~c(R +J;ޅ+$Y0.L sJhPf I[7.FJJ=rf "$o ~JPBJ~Ŵd )NWXmdO 1ѵJ;I?fmmy]I[~3c1domY4$g;VzgҊ敔u=bS 5IĿ+ +ko C OV:5kK.rϒYT貽 CTv_kJRɫyf;n^_@B(P?NM| = 6݋ۅ5v.QK۫JdX9ob*:0.P2`D Pc4I?mF-i٬_0hwB~Hۣ}%P] Dh~Lۙ wVgtd{B01\ i[n0u A(G*z0fΔ0זLF.԰Kad I5Lt՚v.<Bɸ{8G{7r=&06c7I="1hSJ42Ə=gFN%gg׸[mB K_0z/l]0I+" P budgq41W$>#%LܓY܎4Dz?I?Lc݇0(93PB*~l`סq:m&Ag|7]mWa@A(ak/6(ZuaD89QT:=IFCIw3 zR 4lG1\<3B M7nI%&p~>YȂH[aDB m6xC'{e%T4>GA6G3C!9n ,o,rAXɖԹ|vK#׆r/n{r|2[q~ "% ݋쮟lt;h\ ď{P'B8J؆v2˻:?Mr>rsVf#%=본P¨9ߎkȷ涫s\9vJ 6fm+VqV\xr~3>%I$}}99+@u'߂#,e = {YP' 69 t,|42ɠ1|:=Y1)zr"_HW1df+7 7˴v,4:jDrB ԎP|*ZC,'JꠒJy PJ. Au,uZ&?B @aP$.Y^ @gAe|J، _6x %0Ȱݦ^4u5oy/8JNNoJ@(J@(JP_~+pe.|}e`ۋt|7V8/XvW/yoϯ8I_(KN!`-k2Lğ ew,ԝêQ`=ϗa|1=szz:%*j~P۩Rzܞgu5IV/&p\涿?6HIEbm-ܯoޟƞN~ 89Y +{աoF8\<=Kc $a,QO7uǓ܍/ֹ{pERK]=yI9BU^8?_*X._jKcA-N*8eks޼O7+|*1.}?q/'Kz˜bE`i4JYaɋɥŕ *82'6'(`#uoKFGWeĹUˎKqDF:ڄ2sBYk;?uيzMlp=ȑIIA͟c.꩔uݪc]fç ^{i}z{;'kwE=\ѝ8%'}4bMG7r{`*IK-/rI<tm:* udZ.ϰVWUFKԉFP+^mޝ49%_(JTBV/=,1UuW _xacFB/5LcdodwuOt޳>-cpRk>Xq2^{)x<~*ubU]{Gk oޖe ne%WURu_kozkLr椠GqwEC*.EX3l^u[:8׿!Poش>^\I_+ HʢU%@8N P\ +:POj54\~ 6E$|w^=u J:X=7$.=t͓![XXwDNMS7H-,l=VNElsW4*jIw7U%u{[_FIjG_T]¹Rn|QڂN>E(i7MM\[1wZIeٚזlRMgWAVu{(qꥑ6hɭa]COJ8Hre"k{ `U&fۄ`p@:XcTj% l˝*ިx,n(<, P}QV(a# ,=Po,Šy#(='(\fɋxq00}$G5ʆ[kB 5anEX,4)~ *2U|dOK@Ak, %QmqE:hUpQ"ܙxZaW ʸ7nsJ*|nQf,2 =Ԋ-NM:)CU'?MVx)(X6.eu¾_P=dJق`E䩵{qpW.z]*'|X\[q}tɱ`sgYJC,xn8LEC6bG/=MV).|ekyfɒ$î I(GCx)t>t=vDZFYOcm?i47]-uv b\,\ kKlZObOF粔X-FHFѝ6+Y2NɟJظA a␚6cY: 'D=ofk?W}'ͥ _:a]8&Ykcg%iXQg1skW= O vb@ O+z}G5],BɺwXw$Ě%s/+X % Uol0G$7x[띆LԿԻ& )zFyn'UIo^G~Y%WY]Jͭ)}yυjP47aR%2fݗI^d{Qy\ 㾼BVM|XC+JfܞNBE'g^mhT:VԑtŪ.GZ| K7긴oơטrݏ|'IhIX:iQld\+qsx27rn2=t_JøQܭU5P4dQ2s:Izr׍P\*(ywPלƂU^Gύ CC]>[}(lwzT<4bVظC4)yʆV0e;Heƺ#3,)^6vR X93X8ʱt]mZd90JЗ:*[kTMeOj&}^:8:8e8J*< <(pd_Jjl[lR U;0!Ƶ O荽wN=~1riɉFu$ٴ]j}a|m˱IB{kUb4/Z%*:7 w}Zk/uLk+4uV]a5O?/h"[tX:QT+Y6MA U/ _5--[iB Jp-Vڲ w@z\wUT)_pAoߍ%*we_{_M*S.ѳ[Tι=Y˞T? e,a,`:8k|aW^X;݈؃UK{1X )ENJrw %3#}/=`AWRu~vɽ׷f(ɞqeى# 'Vx#Ӣ}y!~F_ktNbFWuZ;i/Jw8}בwlJ606s=my2|, 511 549 1 Adobe ImageReady 1 3_T1IDATxFy@x T`˃zPk2X0,`-Xd=T#7뤒$2MȨso0@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ p! rDg5ּLٹ{Jm-YΤs"؄A# PFb.XA`6cgR;a1 i} 6R%/ ͷ0oHĎ?2v\koo&{l׈9 ) ӦO7!'Y1i#&T7mPk0f)L["5ZDT[ Pvd(OC/ׯ %;% 0E4(LJӐ@OWVԶvA`s͗ "4Y12QY`/i@4 ҄?UI?K |RGh՚0F4];OBd,ls3Px/5}qc@He, XR)\H{,Fw?f?6PȎܗ?f~KUF0tA2J)b>(Vmpu-QYƾv +zb%@arT\\QXhdsV5N=(^ߨRA  1oۤK1_m@]zK ;C-8to;+ qVTdm ?)AzE@)D:RS]*%/a'#@a "9gi+wAR(I)DjmW(N@´E"F[|*w^:mc2hJ%'ڟRKA 8y*dU$ ]^\5ѪR 0,IBҚywK"aJBR-uﭥmR{60@ Jwzp?4M$ s{*yp6W!q? `]W2 Z&^o^SkKARȥR ԇj\L 5DR){ynE1@ s`UJ% 1\!UZω\|A UTJAZaz6O8J)  :ҳ}sAsE M{*i·U_AqܥIHࢤJ)H>+Sy )0DG6{4^jl8nEJ% g#)BgayA`3fH qTHN ?H*ݥO9A`fɥ5ngOpUI 0@TJ,={9;0NMεW)0@`>NnRyN5Am_.a$|Ars;n$ @y һ~{% HuTJ(K6͗KA$pQإ4~QɗpKsT WHA"(d}`9/?NFv丘 nh a dJY>l1uE{ƾk:nRH \xLKƎ=s.q&e@@\ҔeR~g.qb$ 0@+Ji~3vO,bb 3Qf!ʋ?cEHN18fL-_d~1@K1{ҳ1^>$-ڔs^r  {[ISDZ5e rjq6J $]V&0W.dPd7@`ku)Ǖe/[qoo,V% @l. ؒ9j<h1ƞe0@1\mqNa p;BBJ$ ~^65؂=f!11 9oRz-HwkA;FԵB/1x~^.[SZ{pGG@ [@)S Rc= ĬHV"4Gũ\)  TFZlEW0+T&` GD"@lusɩhllKA8\ Eh Bv n.Df 3J[mi/ɔD!O }z̸\  "jŹv3i7KE 9}9a 8]= YvvqLsҩ`g BwMW~sRj۾C:@mL JivkUa?z(VR  pTN=}G{yu{\"z ˺{k^mN:y{@SX8Ψ*_~>yNC8*!/y; F@ >#|=.Hlp@ 7} A5.V~V@ @l ۞J|oɄt׸T 쑀ҳb=s>aj-${ׁALAz[t!C8u(PH *RSRlK ȊM< &0@^ή6"4o̝}u!+]?4 hBzuk^6"` hʚr|׵AX@ 靽q ۈ@3qƾ.`"q!_kۈ׼N VTιv|\T A B{oa]![|/m&KAB Bwwۈ׼119 "aYڽ Cn]t-.|0@F Jݣi=\|:6"PhvabHuKڠ9R"a89K}946"Phvan}j. 86{#KAcOg\م[@!So״/ٖOPA`RSHFt=$EkCsc0n~]Lו\7Vck O7W_!_24_\2 \m}.% 4PW.jCqWr2hƿk)! )0@ nNyomwCk$.v]j皻KޤrA3p9Bz'+jv9[J)0@`; zi绔TӞŗ9hONJ˷0@`=Is>ϥ,NĄG0㮘wȹTIX:$ X@.gYN`.--4w߭SG WFAjw[/_NZ*M2[#']KMzsn|w2 ^'ҫwz/_NZJMغ5a?.mڱY )HX=Ks!]$'礔}{sVhn,cE@ "CͿ{Š͉A.By˟4ɇs|\l8N\x]kRHN SW.-l(_[gDnνEcNK4M @JTIK>oyq>.րW# i+փe4]*$ [4U*Jާk5b!.۹X mrW+F?~Ķ9 otEr/maפ=>;>ؗW}$O_E7ӿ.{Xs-OMoB#;> -Ldcuf&Rdfb/LU~Ow }CFۯYKW'@$)mdmߏ:>)Q,1Mn*9NG=|'kc{VLm0_mi6"Twea:>+ttDOo#+ sgT;",ڟu%e:Oc25M!KudHٗI^OFނ ޾OLtÝ=@p'4lHWս+ ӪA'׻s}p/; ]M[N d6/@RIt$k /IYclc(9 見3LZ@i;([6TR̬xQp,7V|L^ vrAY*rܾXQhZ5LgGeC(S{.^б <# $&06OlR"],=bM[ 8X }b8&ͦld42cs/?.,J MkǵG Yog0 b,oRk=xMuT&LaţbkT/^[3 :i#v^~ 3@ ȇi9 JCH{-btv1+&uuб i ^N0'L?يTs\j3_A Car/b{F's! lC>Yy> W =8)L{e 05R 0@,YFe @&i g%0Q|}= 폱gK꟝ 4PN B&P Ma:Ғ 3],\ bhq @0Bk O, |XzrC@ql'gQ\X$sB@b)LQ!(@+ҕc|: K@0QA^B k@L@@!)L\VF =X>Oӡ0=I@Xim@O P$ U Ugt2 S CXJt֕'o@0E0@CuHa:  }0}6 a0EEWDKaZ&;BPvd @(L^_ ;&WݭC|X @@$N߫R"&@azD 43-/ @b)L BG 4.ZFA8>Гbw. a =qwخX ӳ'1@@,i, K۟IRx&~FAPLȩmx.pC6./=>GA} [5n+Jt8V)vjFZ6>A]I cc+o1`aNQ/߮ó\8ܿOEZ),_~0);IA9 ^'PjʾjJ4h= Y^ зn ~M[ %ptCk1h9ۻkdh>8 8C=vTN=.DؙǕ]d+cFSϰN1"+߳w Ϙl!SFIqA98{)LI\a1ο6$fLeN_3uzWU9ތߚle.OsҁCuTٓs@>+?\Ggk=s̔$v1Y!fH6\|LCFHFbXt`螗*0oHx pc(LgX9~5~T|KR*L~0p)>1 YߏEtK<C9;nZu @wcoǍزy`uX l`Ns˘13xq3r.} a ? \42HFD¶#5uwM۳mncfԷcě|-c5mbZ!ap 6cmC'x[8=3/} a}ض/\❶ DdbznKQhRk'a+o! Zzv)([ߚ^#y$%'$?@5n2n aDNN61'}`77.ߏ]THZ@6{!^~S> r?䫼 @W+i&A؄@5?D7HA 4n@X@pOaC7 ,K OWyXFJ @!)LXFJ <_V')@g#$>9Sj4 Nwb+L/)!@ @&)]vi; @` 7(Lcn @؎'&hRrR"mn͗ <%0.z0]t]J+U;~uP:gޏdL`h_,^'uC>RcRV@ ==@%$R*] ٹSO݌k 6>Jq'C@gyD_v3EKXabɔs0A%p7]k[AIHS2xd%YJ1  gg'GIcLXE\OL;r- Mb{*L;[«+uV"&W{ Kli+oAb@+m9:십0-+!=/im@`:7ٮټpDa!6&Pl_4e%'Y-4k8 Sn?*@33P Ӝ4@`>\}>wyA4[q0%A7\KR栈@OMiujV =0=k@g&Tlq! Tɭ6r*9 .+N/ֿca/B@&v*xc,LCyp!vF Y/L{[? @`vw^ijn \@aJ 02Rc< ~yf t) @H\Ӕ @pS"0MJ`J s~iT  pBr0a8=7o[aroeOb,L~ @XcaZl5q L& '%=~bS0eSc, 2)%AA2}x墘I*b X p@ݥg a4\ g[?!mxAzxlݵ>Q_ : !Q oQ5_5r,'y~y||X(9߻!2+%FX9e0cg'q웈'O.>H:5AC}JZtɗŶ!Y'b!{yqV `'-l;a'l_9֓X OIu'r %`(? >w>mR9ȲFmjUMj㦋fTc "Q@ >=?KA{= ͰԷٽBr5.^eu|cp5| I;LJ9zdxDfc\K6YrJAZ>3g9E؜X}pSx}kV䤽nb/%/$' Y6f\iL¶%4Z}p}r~9o\zcqH^-q/]*$'֖+ ]*Fvl<60ָlBO ]Nh=t @8]kmKTZRM5r2GPLHX< 2A'燮{7sy߾}Ţ ԣ}VG( &MyTc0^Ӿku^wC,2ۓlwOH#S)Ƃ2Dˁ[ӣb`|.%Rk ǐ{Pt VM%+zwOȧʕy*9n%,Pm6~xڷ7iD-|σ4\=s%kYk)Z 뗻{^h K{azplKjB)wR! aUߤT犫ON_֘9 0@Rʤ+}j@ _%XJ%A8֘7OLC51Ǧl#-z"ԗ4cnٿ &a$6H-1g셩^s@Չ9~<ԑHi#5\gv~frh9 R|۳%3'"D0 sIW.>=s(d-,G}}z>sJ? 2^=N.^Vl'v.;W_r.et}Vۘũ=zXdi#,2k?u_xUW#P)w/"vz Hzl ^3}n N y:W@ "M!H}G} {e| ]K@T^)R. f &? 9 V@7|r)b%*THvFAm9pzW{ḇSɹ͓}!7Cy '`g!1vNv/R{/ik%l&N~_g7 B,qN Jm]H >wq\AU~9KC@7v'$l!*8 !( f{ܳ=O%lA|?{}׸\ @$R$0\ P(fE= (Lkih/"o @!Ԣ'G³4t.d@~[\C1_SAU|3@aC?Q!P*gZ߹u$o lki̢8yo+L'ۛ i|`;%`fewЄ Iɮ%20F469@ΤC U(4vz,HLD) 9PlCiRg@  ؛ګ8/@n~+N!H3]zG^y'(؛X'a IOS K $ʹ r"e@k=qOwH-uoFt@Kil_˟0Aࢣg7йRץ@4__*a%ˮR.aJ&RJ% (5dk>X"aسK~xm-J&(5z7"I UXt^}އ峐 r&{nP/ݤ\rlC ӴTI{忔lN (4+7PJ/8.dboWyr)0N Ռ4to]*$'`v2ͥR5nR*a6X%Մ}V@ ["9?Iko3g.L-Tyl\h֒/ 1"VHd?=<د 4HVL% Kg&i&TvOCgKTI؎P+)=*> c{Hk ;7D} ٽr ^4~=ЦuHjj:ǵcHhoڕ"޿9m0N.F =A=71+DYG{'"䕋ݓb'@aZnmSHmWެ3,C#5؛R]'si, (; hJ+Vmv'^KTZ5NiS{Ӥ0Kc_+^NN *LiJaIal\xaZl-fBי8ձT [gߴ\C30޳vJifnocz,7i[_ *C*2I|(LG`kڽ~ T5-H i(Lx: UWXW*޸>}El-:^ǡ 0/B*H'kg:bRIfc)X%T\1af?DZ OhP^KEq&~~@6ǯ׬ԍ84RA8(LY˥3I5 V_` @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ L"P,? ]IENDB`marshmallow-3.26.1/docs/api_reference.rst000066400000000000000000000005501475016050200204020ustar00rootroot00000000000000.. _api: .. toctree:: :caption: API Reference top_level marshmallow.schema marshmallow.fields marshmallow.decorators marshmallow.validate marshmallow.utils marshmallow.exceptions Private API =========== .. toctree:: :caption: Private API marshmallow.types marshmallow.class_registry marshmallow.error_store marshmallow-3.26.1/docs/authors.rst000066400000000000000000000000341475016050200172750ustar00rootroot00000000000000.. include:: ../AUTHORS.rst marshmallow-3.26.1/docs/changelog.rst000066400000000000000000000002071475016050200175410ustar00rootroot00000000000000.. seealso:: Need help upgrading to newer versions? Check out the :doc:`upgrading guide `. .. include:: ../CHANGELOG.rst marshmallow-3.26.1/docs/code_of_conduct.rst000066400000000000000000000251641475016050200207400ustar00rootroot00000000000000Code of Conduct =============== This code of conduct applies to the marshmallow project and all associated projects in the `marshmallow-code `__ organization. .. _coc-when-something-happens: When something happens ---------------------- If you see a Code of Conduct violation, follow these steps: 1. Let the person know that what they did is not appropriate and ask them to stop and/or edit their message(s) or commits. 2. That person should immediately stop the behavior and correct the issue. 3. If this doesn’t happen, or if you're uncomfortable speaking up, :ref:`contact the maintainers `. 4. As soon as possible, a maintainer will look into the issue, and take :ref:`further action (see below) `, starting with a warning, then temporary block, then long-term repo or organization ban. When reporting, please include any relevant details, links, screenshots, context, or other information that may be used to better understand and resolve the situation. **The maintainer team will prioritize the well-being and comfort of the recipients of the violation over the comfort of the violator.** See :ref:`some examples below `. Our pledge ---------- In the interest of fostering an open and welcoming environment, we as contributors and maintainers of this project pledge to making participation in our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, technical preferences, nationality, personal appearance, race, religion, or sexual identity and orientation. Our standards ------------- Examples of behavior that contributes to creating a positive environment include: - Using welcoming and inclusive language. - Being respectful of differing viewpoints and experiences. - Gracefully accepting constructive feedback. - Focusing on what is best for the community. - Showing empathy and kindness towards other community members. - Encouraging and raising up your peers in the project so you can all bask in hacks and glory. Examples of unacceptable behavior by participants include: - The use of sexualized language or imagery and unwelcome sexual attention or advances, including when simulated online. The only exception to sexual topics is channels/spaces specifically for topics of sexual identity. - Casual mention of slavery or indentured servitude and/or false comparisons of one's occupation or situation to slavery. Please consider using or asking about alternate terminology when referring to such metaphors in technology. - Making light of/making mocking comments about trigger warnings and content warnings. - Trolling, insulting/derogatory comments, and personal or political attacks. - Public or private harassment, deliberate intimidation, or threats. - Publishing others' private information, such as a physical or electronic address, without explicit permission. This includes any sort of "outing" of any aspect of someone's identity without their consent. - Publishing private screenshots or quotes of interactions in the context of this project without all quoted users' *explicit* consent. - Publishing of private communication that doesn't have to do with reporting harassment. - Any of the above even when `presented as "ironic" or "joking" `__. - Any attempt to present "reverse-ism" versions of the above as violations. Examples of reverse-isms are "reverse racism", "reverse sexism", "heterophobia", and "cisphobia". - Unsolicited explanations under the assumption that someone doesn't already know it. Ask before you teach! Don't assume what people's knowledge gaps are. - `Feigning or exaggerating surprise `__ when someone admits to not knowing something. - "`Well-actuallies `__" - Other conduct which could reasonably be considered inappropriate in a professional or community setting. Scope ----- This Code of Conduct applies both within spaces involving this project and in other spaces involving community members. This includes the repository, its Pull Requests and Issue tracker, its Twitter community, private email communications in the context of the project, and any events where members of the project are participating, as well as adjacent communities and venues affecting the project's members. Depending on the violation, the maintainers may decide that violations of this code of conduct that have happened outside of the scope of the community may deem an individual unwelcome, and take appropriate action to maintain the comfort and safety of its members. .. _coc-other-community-standards: Other community standards ~~~~~~~~~~~~~~~~~~~~~~~~~ As a project on GitHub, this project is additionally covered by the `GitHub Community Guidelines `__. Enforcement of those guidelines after violations overlapping with the above are the responsibility of the entities, and enforcement may happen in any or all of the services/communities. Maintainer enforcement process ------------------------------ Once the maintainers get involved, they will follow a documented series of steps and do their best to preserve the well-being of project members. This section covers actual concrete steps. .. _coc-contacting-maintainers: Contacting maintainers ~~~~~~~~~~~~~~~~~~~~~~ As a small and young project, we don't yet have a Code of Conduct enforcement team. Hopefully that will be addressed as we grow, but for now, any issues should be addressed to `Steven Loria `__, via `email `__ or any other medium that you feel comfortable with. Using words like "marshmallow code of conduct" in your subject will help make sure your message is noticed quickly. .. _coc-further-enforcement: Further enforcement ~~~~~~~~~~~~~~~~~~~ If you've already followed the :ref:`initial enforcement steps `, these are the steps maintainers will take for further enforcement, as needed: 1. Repeat the request to stop. 2. If the person doubles down, they will be given an official warning. The PR or Issue may be locked. 3. If the behavior continues or is repeated later, the person will be blocked from participating for 24 hours. 4. If the behavior continues or is repeated after the temporary block, a long-term (6-12mo) ban will be used. 5. If after this the behavior still continues, a permanent ban may be enforced. On top of this, maintainers may remove any offending messages, images, contributions, etc, as they deem necessary. Maintainers reserve full rights to skip any of these steps, at their discretion, if the violation is considered to be a serious and/or immediate threat to the health and well-being of members of the community. These include any threats, serious physical or verbal attacks, and other such behavior that would be completely unacceptable in any social setting that puts our members at risk. Members expelled from events or venues with any sort of paid attendance will not be refunded. Who watches the watchers? ~~~~~~~~~~~~~~~~~~~~~~~~~ Maintainers and other leaders who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. These may include anything from removal from the maintainer team to a permanent ban from the community. Additionally, as a project hosted on GitHub, :ref:`their Code of Conduct may be applied against maintainers of this project `, externally of this project's procedures. .. _coc-enforcement-examples: Enforcement examples -------------------- The best case ~~~~~~~~~~~~~ The vast majority of situations work out like this. This interaction is common, and generally positive. Alex: "Yeah I used X and it was really crazy!" Patt (not a maintainer): "Hey, could you not use that word? What about 'ridiculous' instead?" Alex: "oh sorry, sure." -> edits old comment to say "it was really confusing!" The maintainer case ~~~~~~~~~~~~~~~~~~~ Sometimes, though, you need to get maintainers involved. Maintainers will do their best to resolve conflicts, but people who were harmed by something **will take priority**. Patt: "Honestly, sometimes I just really hate using $library and anyone who uses it probably sucks at their job." Alex: "Whoa there, could you dial it back a bit? There's a CoC thing about attacking folks' tech use like that." Patt: "I'm not attacking anyone, what's your problem?" Alex: "@maintainers hey uh. Can someone look at this issue? Patt is getting a bit aggro. I tried to nudge them about it, but nope." KeeperOfCommitBits: (on issue) "Hey Patt, maintainer here. Could you tone it down? This sort of attack is really not okay in this space." Patt: "Leave me alone I haven't said anything bad wtf is wrong with you." KeeperOfCommitBits: (deletes user's comment), "@patt I mean it. Please refer to the CoC over at (URL to this CoC) if you have questions, but you can consider this an actual warning. I'd appreciate it if you reworded your messages in this thread, since they made folks there uncomfortable. Let's try and be kind, yeah?" Patt: "@KeeperOfCommitBits Okay sorry. I'm just frustrated and I'm kinda burnt out and I guess I got carried away. I'll DM Alex a note apologizing and edit my messages. Sorry for the trouble." KeeperOfCommitBits: "@patt Thanks for that. I hear you on the stress. Burnout sucks :/. Have a good one!" The nope case ~~~~~~~~~~~~~ PepeTheFrog🐸: "Hi, I am a literal actual nazi and I think white supremacists are quite fashionable." Patt: "NOOOOPE. OH NOPE NOPE." Alex: "JFC NO. NOPE. @KeeperOfCommitBits NOPE NOPE LOOK HERE" KeeperOfCommitBits: "👀 Nope. NOPE NOPE NOPE. 🔥" PepeTheFrog🐸 has been banned from all organization or user repositories belonging to KeeperOfCommitBits. Attribution ----------- This Code of Conduct is based on `Trio's Code of Conduct `_, which is based on the `WeAllJS Code of Conduct `__, which is itself based on `Contributor Covenant `__, version 1.4, available at https://contributor-covenant.org/version/1/4, and the LGBTQ in Technology Slack `Code of Conduct `__. marshmallow-3.26.1/docs/conf.py000066400000000000000000000044761475016050200163730ustar00rootroot00000000000000import importlib.metadata extensions = [ "autodocsumm", "sphinx.ext.autodoc", "sphinx.ext.autodoc.typehints", "sphinx.ext.intersphinx", "sphinx.ext.viewcode", "sphinx_copybutton", "sphinx_issues", "sphinxext.opengraph", ] primary_domain = "py" default_role = "py:obj" intersphinx_mapping = {"python": ("https://docs.python.org/3", None)} issues_github_path = "marshmallow-code/marshmallow" source_suffix = ".rst" master_doc = "index" project = "marshmallow" copyright = "Steven Loria and contributors" # noqa: A001 version = release = importlib.metadata.version("marshmallow") exclude_patterns = ["_build"] # Ignore WARNING: more than one target found for cross-reference 'Schema': marshmallow.schema.Schema, marshmallow.Schema suppress_warnings = ["ref.python"] # THEME html_theme = "furo" html_theme_options = { "light_logo": "marshmallow-logo-with-title.png", "dark_logo": "marshmallow-logo-with-title-for-dark-theme.png", "source_repository": "https://github.com/marshmallow-code/marshmallow", "source_branch": "3.x-line", "source_directory": "docs/", "sidebar_hide_name": True, "light_css_variables": { # Serif system font stack: https://systemfontstack.com/ "font-stack": "Iowan Old Style, Apple Garamond, Baskerville, Times New Roman, Droid Serif, Times, Source Serif Pro, serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol;", }, "top_of_page_buttons": ["view"], } pygments_dark_style = "lightbulb" html_favicon = "_static/favicon.ico" html_static_path = ["_static"] html_css_files = ["custom.css"] html_copy_source = False # Don't copy source files to _build/sources html_show_sourcelink = False # Don't link to source files ogp_image = "_static/marshmallow-logo-200.png" # Strip the dollar prompt when copying code # https://sphinx-copybutton.readthedocs.io/en/latest/use.html#strip-and-configure-input-prompts-for-code-cells copybutton_prompt_text = "$ " autodoc_default_options = { "exclude-members": "__new__", # Don't show signatures in the summary tables "autosummary-nosignatures": True, # Don't render summaries for classes within modules "autosummary-no-nesting": True, } # Only display type hints next to params but not within the signature # to avoid the signature from getting too long autodoc_typehints = "description" marshmallow-3.26.1/docs/contributing.rst000066400000000000000000000000411475016050200203150ustar00rootroot00000000000000.. include:: ../CONTRIBUTING.rst marshmallow-3.26.1/docs/custom_fields.rst000066400000000000000000000141461475016050200204610ustar00rootroot00000000000000Custom fields ============= There are three ways to create a custom-formatted field for a `Schema `: - Create a custom :class:`Field ` class - Use a :class:`Method ` field - Use a :class:`Function ` field The method you choose will depend on the manner in which you intend to reuse the field. Creating a field class ---------------------- To create a custom field class, create a subclass of :class:`marshmallow.fields.Field` and implement its :meth:`_serialize ` and/or :meth:`_deserialize ` methods. .. code-block:: python from marshmallow import fields, ValidationError class PinCode(fields.Field): """Field that serializes to a string of numbers and deserializes to a list of numbers. """ def _serialize(self, value, attr, obj, **kwargs): if value is None: return "" return "".join(str(d) for d in value) def _deserialize(self, value, attr, data, **kwargs): try: return [int(c) for c in value] except ValueError as error: raise ValidationError("Pin codes must contain only digits.") from error class UserSchema(Schema): name = fields.String() email = fields.String() created_at = fields.DateTime() pin_code = PinCode() Method fields ------------- A :class:`Method ` field will serialize to the value returned by a method of the Schema. The method must take an ``obj`` parameter which is the object to be serialized. .. code-block:: python class UserSchema(Schema): name = fields.String() email = fields.String() created_at = fields.DateTime() since_created = fields.Method("get_days_since_created") def get_days_since_created(self, obj): return dt.datetime.now().day - obj.created_at.day Function fields --------------- A :class:`Function ` field will serialize the value of a function that is passed directly to it. Like a :class:`Method ` field, the function must take a single argument ``obj``. .. code-block:: python class UserSchema(Schema): name = fields.String() email = fields.String() created_at = fields.DateTime() uppername = fields.Function(lambda obj: obj.name.upper()) `Method` and `Function` field deserialization --------------------------------------------- Both :class:`Function ` and :class:`Method ` receive an optional ``deserialize`` argument which defines how the field should be deserialized. The method or function passed to ``deserialize`` receives the input value for the field. .. code-block:: python class UserSchema(Schema): # `Method` takes a method name (str), Function takes a callable balance = fields.Method("get_balance", deserialize="load_balance") def get_balance(self, obj): return obj.income - obj.debt def load_balance(self, value): return float(value) schema = UserSchema() result = schema.load({"balance": "100.00"}) result["balance"] # => 100.0 .. _adding-context: Adding context to `Method` and `Function` fields ------------------------------------------------ .. warning:: The ``context`` attribute is deprecated and will be removed in marshmallow 4. Use `contextvars.ContextVar` for passing context to fields, pre-/post-processing methods, and validators instead. marshmallow 4 will also provide an `experimental helper API `_ for using context. A :class:`Function ` or :class:`Method ` field may need information about its environment to know how to serialize a value. In these cases, you can set the ``context`` attribute (a dictionary) of a `Schema `. :class:`Function ` and :class:`Method ` fields will have access to this dictionary. As an example, you might want your ``UserSchema`` to output whether or not a ``User`` is the author of a ``Blog`` or whether a certain word appears in a ``Blog's`` title. .. code-block:: python class UserSchema(Schema): name = fields.String() # Function fields optionally receive context argument is_author = fields.Function(lambda user, context: user == context["blog"].author) likes_bikes = fields.Method("writes_about_bikes") def writes_about_bikes(self, user): return "bicycle" in self.context["blog"].title.lower() schema = UserSchema() user = User("Freddie Mercury", "fred@queen.com") blog = Blog("Bicycle Blog", author=user) schema.context = {"blog": blog} result = schema.dump(user) result["is_author"] # => True result["likes_bikes"] # => True Customizing error messages -------------------------- Validation error messages for fields can be configured at the class or instance level. At the class level, default error messages are defined as a mapping from error codes to error messages. .. code-block:: python from marshmallow import fields class MyDate(fields.Date): default_error_messages = {"invalid": "Please provide a valid date."} .. note:: A `Field's` ``default_error_messages`` dictionary gets merged with its parent classes' ``default_error_messages`` dictionaries. Error messages can also be passed to a `Field's` constructor. .. code-block:: python from marshmallow import Schema, fields class UserSchema(Schema): name = fields.Str( required=True, error_messages={"required": "Please provide a name."} ) Next steps ---------- - Need to add schema-level validation, post-processing, or error handling behavior? See the :doc:`extending/index` page. - For example applications using marshmallow, check out the :doc:`examples/index` page. marshmallow-3.26.1/docs/dashing.json000066400000000000000000000006111475016050200173670ustar00rootroot00000000000000{ "name": "marshmallow", "package": "marshmallow", "index": "_build/index.html", "selectors": { "dl.class dt": { "type": "Class", "attr": "id" }, "dl.exception dt": { "type": "Exception", "attr": "id" }, "dl.function dt": { "type": "Function", "attr": "id" } }, "icon32x32": "", "allowJS": false, "ExternalURL": "" } marshmallow-3.26.1/docs/donate.rst000066400000000000000000000005321475016050200170650ustar00rootroot00000000000000****** Donate ****** If you find marshmallow useful, please consider supporting the team with a donation: .. image:: https://opencollective.com/marshmallow/donate/button@2x.png :target: https://opencollective.com/marshmallow :alt: Donate to our Open Collective :height: 50px Your donation keeps marshmallow healthy and maintained. marshmallow-3.26.1/docs/examples/000077500000000000000000000000001475016050200166775ustar00rootroot00000000000000marshmallow-3.26.1/docs/examples/index.rst000066400000000000000000000007721475016050200205460ustar00rootroot00000000000000******** Examples ******** The below examples demonstrate how to use marshmallow in various contexts. To run each example, you will need to have `uv `_ installed. The examples use `PEP 723 inline metadata `_ to declare the dependencies of each script. ``uv`` will install the dependencies automatically when running these scripts. .. toctree:: :maxdepth: 1 validating_package_json quotes_api inflection marshmallow-3.26.1/docs/examples/inflection.rst000066400000000000000000000011531475016050200215630ustar00rootroot00000000000000***************************** Inflection (camel-cased keys) ***************************** HTTP APIs will often use camel-cased keys for their input and output representations. This example shows how you can use the `Schema.on_bind_field ` hook to automatically inflect keys. .. literalinclude:: ../../examples/inflection_example.py :language: python To run the example: .. code-block:: shell-session $ uv run examples/inflection_example.py Loaded data: {'first_name': 'David', 'last_name': 'Bowie'} Dumped data: {'firstName': 'David', 'lastName': 'Bowie'} marshmallow-3.26.1/docs/examples/quotes_api.rst000066400000000000000000000047121475016050200216060ustar00rootroot00000000000000******************************* Quotes API (Flask + SQLAlchemy) ******************************* Below is a full example of a REST API for a quotes app using `Flask `_ and `SQLAlchemy `_ with marshmallow. It demonstrates a number of features, including: - Custom validation - Nesting fields - Using ``dump_only=True`` to specify read-only fields - Output filtering using the ``only`` parameter - Using `@pre_load ` to preprocess input data. .. literalinclude:: ../../examples/flask_example.py :language: python **Using The API** Run the app. .. code-block:: shell-session $ uv run examples/flask_example.py We'll use the `httpie cli `_ to send requests Install it with ``uv``. .. code-block:: shell-session $ uv tool install httpie First we'll POST some quotes. .. code-block:: shell-session $ http POST :5000/quotes/ author="Tim Peters" content="Beautiful is better than ugly." $ http POST :5000/quotes/ author="Tim Peters" content="Now is better than never." $ http POST :5000/quotes/ author="Peter Hintjens" content="Simplicity is always better than functionality." If we provide invalid input data, we get 400 error response. Let's omit "author" from the input data. .. code-block:: shell-session $ http POST :5000/quotes/ content="I have no author" { "author": [ "Data not provided." ] } Now we can GET a list of all the quotes. .. code-block:: shell-session $ http :5000/quotes/ { "quotes": [ { "content": "Beautiful is better than ugly.", "id": 1 }, { "content": "Now is better than never.", "id": 2 }, { "content": "Simplicity is always better than functionality.", "id": 3 } ] } We can also GET the quotes for a single author. .. code-block:: shell-session $ http :5000/authors/1 { "author": { "first": "Tim", "formatted_name": "Peters, Tim", "id": 1, "last": "Peters" }, "quotes": [ { "content": "Beautiful is better than ugly.", "id": 1 }, { "content": "Now is better than never.", "id": 2 } ] } marshmallow-3.26.1/docs/examples/validating_package_json.rst000066400000000000000000000030431475016050200242570ustar00rootroot00000000000000*************************** Validating ``package.json`` *************************** marshmallow can be used to validate configuration according to a schema. Below is a schema that could be used to validate ``package.json`` files. This example demonstrates the following features: - Validation and deserialization using `Schema.load ` - :doc:`Custom fields <../custom_fields>` - Specifying deserialization keys using ``data_key`` - Including unknown keys using ``unknown = INCLUDE`` .. literalinclude:: ../../examples/package_json_example.py :language: python Given the following ``package.json`` file... .. literalinclude:: ../../examples/package.json :language: json We can validate it using the above script. .. code-block:: shell-session $ uv run examples/package_json_example.py < examples/package.json {'description': 'The Pythonic JavaScript toolkit', 'dev_dependencies': {'pest': '^23.4.1'}, 'license': 'MIT', 'main': 'index.js', 'name': 'dunderscore', 'scripts': {'test': 'pest'}, 'version': } Notice that our custom field deserialized the version string to a ``Version`` object. But if we pass an invalid package.json file... .. literalinclude:: ../../examples/invalid_package.json :language: json We see the corresponding error messages. .. code-block:: shell-session $ uv run examples/package_json_example.py < examples/invalid_package.json ERROR: package.json is invalid {'homepage': ['Not a valid URL.'], 'version': ['Not a valid version.']} marshmallow-3.26.1/docs/extending/000077500000000000000000000000001475016050200170465ustar00rootroot00000000000000marshmallow-3.26.1/docs/extending/custom_error_handling.rst000066400000000000000000000017771475016050200242030ustar00rootroot00000000000000Custom error handling ===================== By default, `Schema.load ` will raise a :exc:`ValidationError ` if passed invalid data. You can specify a custom error-handling function for a :class:`Schema` by overriding the `handle_error ` method. The method receives the :exc:`ValidationError ` and the original input data to be deserialized. .. code-block:: python import logging from marshmallow import Schema, fields class AppError(Exception): pass class UserSchema(Schema): email = fields.Email() def handle_error(self, exc, data, **kwargs): """Log and raise our custom exception when (de)serialization fails.""" logging.error(exc.messages) raise AppError("An error occurred with input: {0}".format(data)) schema = UserSchema() schema.load({"email": "invalid-email"}) # raises AppError marshmallow-3.26.1/docs/extending/custom_error_messages.rst000066400000000000000000000021001475016050200242030ustar00rootroot00000000000000Custom error messages ===================== To customize the schema-level error messages that `load ` and `loads ` use when raising a `ValidationError `, override the `error_messages ` class variable: .. code-block:: python class MySchema(Schema): error_messages = { "unknown": "Custom unknown field error message.", "type": "Custom invalid type error message.", } Field-level error message defaults can be set on `Field.default_error_messages `. .. code-block:: python from marshmallow import Schema, fields fields.Field.default_error_messages["required"] = "You missed something!" class ArtistSchema(Schema): name = fields.Str(required=True) label = fields.Str(required=True, error_messages={"required": "Label missing."}) print(ArtistSchema().validate({})) # {'label': ['Label missing.'], 'name': ['You missed something!']} marshmallow-3.26.1/docs/extending/custom_options.rst000066400000000000000000000046461475016050200226770ustar00rootroot00000000000000Custom `class Meta ` options ===================================================== `class Meta ` options are a way to configure and modify a `Schema's ` behavior. See `marshmallow.Schema.Meta` for a listing of available options. You can add custom `class Meta ` options by subclassing `marshmallow.SchemaOpts`. Example: Enveloping, revisited ------------------------------ Let's build upon the :ref:`previous enveloping implementation ` above for adding an envelope to serialized output. This time, we will allow the envelope key to be customizable with `class Meta ` options. :: # Example outputs { 'user': { 'name': 'Keith', 'email': 'keith@stones.com' } } # List output { 'users': [{'name': 'Keith'}, {'name': 'Mick'}] } First, we'll add our namespace configuration to a custom options class. .. code-block:: python from marshmallow import Schema, SchemaOpts class NamespaceOpts(SchemaOpts): """Same as the default class Meta options, but adds "name" and "plural_name" options for enveloping. """ def __init__(self, meta, **kwargs): SchemaOpts.__init__(self, meta, **kwargs) self.name = getattr(meta, "name", None) self.plural_name = getattr(meta, "plural_name", self.name) Then we create a custom :class:`Schema` that uses our options class. .. code-block:: python class NamespacedSchema(Schema): OPTIONS_CLASS = NamespaceOpts @pre_load(pass_many=True) def unwrap_envelope(self, data, many, **kwargs): key = self.opts.plural_name if many else self.opts.name return data[key] @post_dump(pass_many=True) def wrap_with_envelope(self, data, many, **kwargs): key = self.opts.plural_name if many else self.opts.name return {key: data} Our application schemas can now inherit from our custom schema class. .. code-block:: python class UserSchema(NamespacedSchema): name = fields.String() email = fields.Email() class Meta: name = "user" plural_name = "users" ser = UserSchema() user = User("Keith", email="keith@stones.com") result = ser.dump(user) result # {"user": {"name": "Keith", "email": "keith@stones.com"}} marshmallow-3.26.1/docs/extending/index.rst000066400000000000000000000005101475016050200207030ustar00rootroot00000000000000Extending schemas ================= The guides below demonstrate how to extend schemas in various ways. .. toctree:: :maxdepth: 1 pre_and_post_processing_methods schema_validation using_original_input_data overriding_attribute_access custom_error_handling custom_options using_context custom_error_messages marshmallow-3.26.1/docs/extending/overriding_attribute_access.rst000066400000000000000000000013461475016050200253600ustar00rootroot00000000000000Overriding how attributes are accessed ====================================== By default, marshmallow uses `utils.get_value ` to pull attributes from various types of objects for serialization. This will work for *most* use cases. However, if you want to specify how values are accessed from an object, you can override the :meth:`get_attribute ` method. .. code-block:: python class UserDictSchema(Schema): name = fields.Str() email = fields.Email() # If we know we're only serializing dictionaries, we can # use dict.get for all input objects def get_attribute(self, obj, key, default): return obj.get(key, default) marshmallow-3.26.1/docs/extending/pre_and_post_processing_methods.rst000066400000000000000000000152751475016050200262460ustar00rootroot00000000000000Pre-processing and post-processing methods ========================================== Decorator API ------------- Data pre-processing and post-processing methods can be registered using the `pre_load `, `post_load `, `pre_dump `, and `post_dump ` decorators. .. code-block:: python from marshmallow import Schema, fields, post_load class UserSchema(Schema): name = fields.Str() slug = fields.Str() @post_load def slugify_name(self, in_data, **kwargs): in_data["slug"] = in_data["slug"].lower().strip().replace(" ", "-") return in_data schema = UserSchema() result = schema.load({"name": "Steve", "slug": "Steve Loria "}) result["slug"] # => 'steve-loria' Passing "many" -------------- By default, pre- and post-processing methods receive one object/datum at a time, transparently handling the ``many`` parameter passed to the ``Schema``'s `~marshmallow.Schema.dump`/`~marshmallow.Schema.load` method at runtime. In cases where your pre- and post-processing methods needs to handle the input collection when processing multiple objects, add ``pass_many=True`` to the method decorators. Your method will then receive the input data (which may be a single datum or a collection, depending on the dump/load call). .. _enveloping_1: Example: Enveloping ------------------- One common use case is to wrap data in a namespace upon serialization and unwrap the data during deserialization. .. code-block:: python from marshmallow import Schema, fields, pre_load, post_load, post_dump class BaseSchema(Schema): # Custom options __envelope__ = {"single": None, "many": None} __model__ = User def get_envelope_key(self, many): """Helper to get the envelope key.""" key = self.__envelope__["many"] if many else self.__envelope__["single"] assert key is not None, "Envelope key undefined" return key @pre_load(pass_many=True) def unwrap_envelope(self, data, many, **kwargs): key = self.get_envelope_key(many) return data[key] @post_dump(pass_many=True) def wrap_with_envelope(self, data, many, **kwargs): key = self.get_envelope_key(many) return {key: data} @post_load def make_object(self, data, **kwargs): return self.__model__(**data) class UserSchema(BaseSchema): __envelope__ = {"single": "user", "many": "users"} __model__ = User name = fields.Str() email = fields.Email() user_schema = UserSchema() user = User("Mick", email="mick@stones.org") user_data = user_schema.dump(user) # {'user': {'email': 'mick@stones.org', 'name': 'Mick'}} users = [ User("Keith", email="keith@stones.org"), User("Charlie", email="charlie@stones.org"), ] users_data = user_schema.dump(users, many=True) # {'users': [{'email': 'keith@stones.org', 'name': 'Keith'}, # {'email': 'charlie@stones.org', 'name': 'Charlie'}]} user_objs = user_schema.load(users_data, many=True) # [, ] Raising errors in pre-/post-processor methods --------------------------------------------- Pre- and post-processing methods may raise a `ValidationError `. By default, errors will be stored on the ``"_schema"`` key in the errors dictionary. .. code-block:: python from marshmallow import Schema, fields, ValidationError, pre_load class BandSchema(Schema): name = fields.Str() @pre_load def unwrap_envelope(self, data, **kwargs): if "data" not in data: raise ValidationError('Input data must have a "data" key.') return data["data"] sch = BandSchema() try: sch.load({"name": "The Band"}) except ValidationError as err: err.messages # {'_schema': ['Input data must have a "data" key.']} If you want to store and error on a different key, pass the key name as the second argument to `ValidationError `. .. code-block:: python from marshmallow import Schema, fields, ValidationError, pre_load class BandSchema(Schema): name = fields.Str() @pre_load def unwrap_envelope(self, data, **kwargs): if "data" not in data: raise ValidationError( 'Input data must have a "data" key.', "_preprocessing" ) return data["data"] sch = BandSchema() try: sch.load({"name": "The Band"}) except ValidationError as err: err.messages # {'_preprocessing': ['Input data must have a "data" key.']} Pre-/post-processor invocation order ------------------------------------ In summary, the processing pipeline for deserialization is as follows: 1. ``@pre_load(pass_many=True)`` methods 2. ``@pre_load(pass_many=False)`` methods 3. ``load(in_data, many)`` (validation and deserialization) 4. ``@validates`` methods (field validators) 5. ``@validates_schema`` methods (schema validators) 6. ``@post_load(pass_many=True)`` methods 7. ``@post_load(pass_many=False)`` methods The pipeline for serialization is similar, except that the ``pass_many=True`` processors are invoked *after* the ``pass_many=False`` processors and there are no validators. 1. ``@pre_dump(pass_many=False)`` methods 2. ``@pre_dump(pass_many=True)`` methods 3. ``dump(obj, many)`` (serialization) 4. ``@post_dump(pass_many=False)`` methods 5. ``@post_dump(pass_many=True)`` methods .. warning:: You may register multiple processor methods on a Schema. Keep in mind, however, that **the invocation order of decorated methods of the same type is not guaranteed**. If you need to guarantee order of processing steps, you should put them in the same method. .. code-block:: python from marshmallow import Schema, fields, pre_load # YES class MySchema(Schema): field_a = fields.Raw() @pre_load def preprocess(self, data, **kwargs): step1_data = self.step1(data) step2_data = self.step2(step1_data) return step2_data def step1(self, data): ... # Depends on step1 def step2(self, data): ... # NO class MySchema(Schema): field_a = fields.Raw() @pre_load def step1(self, data, **kwargs): ... # Depends on step1 @pre_load def step2(self, data, **kwargs): ... marshmallow-3.26.1/docs/extending/schema_validation.rst000066400000000000000000000055551475016050200232640ustar00rootroot00000000000000.. _schema_validation: Schema-level validation ======================= You can register schema-level validation functions for a :class:`Schema` using the `marshmallow.validates_schema ` decorator. By default, schema-level validation errors will be stored on the ``_schema`` key of the errors dictionary. .. code-block:: python from marshmallow import Schema, fields, validates_schema, ValidationError class NumberSchema(Schema): field_a = fields.Integer() field_b = fields.Integer() @validates_schema def validate_numbers(self, data, **kwargs): if data["field_b"] >= data["field_a"]: raise ValidationError("field_a must be greater than field_b") schema = NumberSchema() try: schema.load({"field_a": 1, "field_b": 2}) except ValidationError as err: err.messages["_schema"] # => ["field_a must be greater than field_b"] Storing errors on specific fields --------------------------------- It is possible to report errors on fields and subfields using a `dict`. When multiple schema-leval validator return errors, the error structures are merged together in the :exc:`ValidationError ` raised at the end of the validation. .. code-block:: python from marshmallow import Schema, fields, validates_schema, ValidationError class NumberSchema(Schema): field_a = fields.Integer() field_b = fields.Integer() field_c = fields.Integer() field_d = fields.Integer() @validates_schema def validate_lower_bound(self, data, **kwargs): errors = {} if data["field_b"] <= data["field_a"]: errors["field_b"] = ["field_b must be greater than field_a"] if data["field_c"] <= data["field_a"]: errors["field_c"] = ["field_c must be greater than field_a"] if errors: raise ValidationError(errors) @validates_schema def validate_upper_bound(self, data, **kwargs): errors = {} if data["field_b"] >= data["field_d"]: errors["field_b"] = ["field_b must be lower than field_d"] if data["field_c"] >= data["field_d"]: errors["field_c"] = ["field_c must be lower than field_d"] if errors: raise ValidationError(errors) schema = NumberSchema() try: schema.load({"field_a": 3, "field_b": 2, "field_c": 1, "field_d": 0}) except ValidationError as err: err.messages # => { # 'field_b': [ # 'field_b must be greater than field_a', # 'field_b must be lower than field_d' # ], # 'field_c': [ # 'field_c must be greater than field_a', # 'field_c must be lower than field_d' # ] # } marshmallow-3.26.1/docs/extending/using_context.rst000066400000000000000000000015171475016050200224750ustar00rootroot00000000000000Using context ============= .. warning:: The ``context`` attribute is deprecated and will be removed in marshmallow 4. Use `contextvars.ContextVar` for passing context to fields, pre-/post-processing methods, and validators instead. marshmallow 4 will also provide an `experimental helper API `_ for using context. The ``context`` attribute of a `Schema ` is a general-purpose store for extra information that may be needed for (de)serialization. It may be used in both ``Schema`` and ``Field`` methods. .. code-block:: python schema = UserSchema() # Make current HTTP request available to # custom fields, schema methods, schema validators, etc. schema.context["request"] = request schema.dump(user) marshmallow-3.26.1/docs/extending/using_original_input_data.rst000066400000000000000000000016701475016050200250250ustar00rootroot00000000000000Using original input data ------------------------- If you want to use the original, unprocessed input, you can add ``pass_original=True`` to `post_load ` or `validates_schema `. .. code-block:: python from marshmallow import Schema, fields, post_load, ValidationError class MySchema(Schema): foo = fields.Int() bar = fields.Int() @post_load(pass_original=True) def add_baz_to_bar(self, data, original_data, **kwargs): baz = original_data.get("baz") if baz: data["bar"] = data["bar"] + baz return data schema = MySchema() schema.load({"foo": 1, "bar": 2, "baz": 3}) # {'foo': 1, 'bar': 5} .. seealso:: The default behavior for unspecified fields can be controlled with the ``unknown`` option, see :ref:`Handling Unknown Fields ` for more information. marshmallow-3.26.1/docs/index.rst000066400000000000000000000035311475016050200167240ustar00rootroot00000000000000.. meta:: :description: marshmallow is an ORM/ODM/framework-agnostic library for converting complex datatypes, such as objects, to and from native Python datatypes. *********** marshmallow *********** *Object serialization and deserialization, lightweight and fluffy.* Release v\ |version|. (:doc:`changelog`) ---- .. include:: ../README.rst :start-after: .. start elevator-pitch :end-before: .. end elevator-pitch Ready to get started? Go on to the :doc:`quickstart` or check out some :doc:`examples `. Upgrading from an older version? ================================ See the :doc:`upgrading` page for notes on getting your code up-to-date with the latest version. Why another library? ===================== See :doc:`this document ` to learn about what makes marshmallow unique. Sponsors ======== .. include:: ../README.rst :start-after: .. start sponsors :end-before: .. end sponsors .. toctree:: :maxdepth: 1 :hidden: :titlesonly: Home Usage guide =========== .. toctree:: :caption: Usage guide :maxdepth: 2 install quickstart nesting custom_fields extending/index examples/index API reference ============= .. toctree:: :caption: API reference :maxdepth: 1 api_reference Project info ============= .. toctree:: :caption: Project info :maxdepth: 1 why changelog upgrading whos_using license authors contributing code_of_conduct kudos donate .. toctree:: :hidden: :caption: Useful links marshmallow @ PyPI marshmallow @ GitHub Issue Tracker Ecosystem marshmallow-3.26.1/docs/install.rst000066400000000000000000000013551475016050200172650ustar00rootroot00000000000000Installation ============ **marshmallow** has no external dependencies other than the `packaging` library. Installing/upgrading from the PyPI ---------------------------------- To install the latest stable version from the PyPI: .. code-block:: shell-session $ pip install -U marshmallow To install the latest pre-release version from the PyPI: .. code-block:: shell-session $ pip install -U marshmallow --pre Get the bleeding edge version ----------------------------- To get the latest development version of marshmallow, run .. code-block:: shell-session $ pip install -U git+https://github.com/marshmallow-code/marshmallow.git@dev .. seealso:: Need help upgrading to newer releases? See the :doc:`upgrading` page. marshmallow-3.26.1/docs/kudos.rst000066400000000000000000000005051475016050200167400ustar00rootroot00000000000000***** Kudos ***** A hat tip to `Django Rest Framework`_ , `Flask-RESTful`_, and `colander`_ for ideas and API design. .. _Flask-RESTful: https://flask-restful.readthedocs.io/en/latest/ .. _Django Rest Framework: https://django-rest-framework.org/ .. _colander: https://docs.pylonsproject.org/projects/colander/en/latest/ marshmallow-3.26.1/docs/license.rst000066400000000000000000000000601475016050200172310ustar00rootroot00000000000000License ======= .. literalinclude:: ../LICENSE marshmallow-3.26.1/docs/marshmallow.class_registry.rst000066400000000000000000000001301475016050200231670ustar00rootroot00000000000000Class registry ============== .. automodule:: marshmallow.class_registry :members: marshmallow-3.26.1/docs/marshmallow.decorators.rst000066400000000000000000000001361475016050200223050ustar00rootroot00000000000000Decorators ========== .. automodule:: marshmallow.decorators :members: :autosummary: marshmallow-3.26.1/docs/marshmallow.error_store.rst000066400000000000000000000001451475016050200225050ustar00rootroot00000000000000Error store =========== .. automodule:: marshmallow.error_store :members: :private-members: marshmallow-3.26.1/docs/marshmallow.exceptions.rst000066400000000000000000000001141475016050200223150ustar00rootroot00000000000000Exceptions ========== .. automodule:: marshmallow.exceptions :members: marshmallow-3.26.1/docs/marshmallow.fields.rst000066400000000000000000000005131475016050200214050ustar00rootroot00000000000000.. _api_fields: Fields ====== Base Field Class ---------------- .. autoclass:: marshmallow.fields.Field :private-members: Field subclasses ---------------- .. automodule:: marshmallow.fields :members: :autosummary: :exclude-members: Field, default_error_messages, mapping_type, num_type, DESERIALIZATION_CLASS marshmallow-3.26.1/docs/marshmallow.schema.rst000066400000000000000000000002521475016050200213770ustar00rootroot00000000000000Schema ====== .. autoclass:: marshmallow.schema.Schema :members: :autosummary: :exclude-members: OPTIONS_CLASS .. autoclass:: marshmallow.schema.SchemaOpts marshmallow-3.26.1/docs/marshmallow.types.rst000066400000000000000000000000751475016050200213060ustar00rootroot00000000000000Types ===== .. automodule:: marshmallow.types :members: marshmallow-3.26.1/docs/marshmallow.utils.rst000066400000000000000000000001251475016050200212760ustar00rootroot00000000000000Utility functions ================= .. automodule:: marshmallow.utils :members: marshmallow-3.26.1/docs/marshmallow.validate.rst000066400000000000000000000001611475016050200217270ustar00rootroot00000000000000.. _api_validators: Validators ========== .. automodule:: marshmallow.validate :members: :autosummary: marshmallow-3.26.1/docs/nesting.rst000066400000000000000000000216631475016050200172720ustar00rootroot00000000000000Nesting schemas =============== Schemas can be nested to represent relationships between objects (e.g. foreign key relationships). For example, a ``Blog`` may have an author represented by a ``User``. And a ``User`` may have many friends, each of which is a ``User``. .. code-block:: python from __future__ import annotations # Enable newer type annotation syntax import datetime as dt from dataclasses import dataclass, field @dataclass class User: name: str email: str created_at: dt.datetime = field(default_factory=dt.datetime.now) friends: list[User] = field(default_factory=list) employer: User | None = None @dataclass class Blog: title: str author: User Use a :class:`Nested ` field to represent the relationship, passing in a nested schema. .. code-block:: python from marshmallow import Schema, fields class UserSchema(Schema): name = fields.String() email = fields.Email() created_at = fields.DateTime() class BlogSchema(Schema): title = fields.String() author = fields.Nested(UserSchema) The serialized blog will have the nested user representation. .. code-block:: python from pprint import pprint user = User(name="Monty", email="monty@python.org") blog = Blog(title="Something Completely Different", author=user) result = BlogSchema().dump(blog) pprint(result) # {'title': u'Something Completely Different', # 'author': {'name': u'Monty', # 'email': u'monty@python.org', # 'created_at': '2014-08-17T14:58:57.600623+00:00'}} .. note:: If the field is a collection of nested objects, pass the `Nested ` field to `List `. .. code-block:: python collaborators = fields.List(fields.Nested(UserSchema)) .. _specifying-nested-fields: Specifying which fields to nest ------------------------------- You can explicitly specify which attributes of the nested objects you want to (de)serialize with the ``only`` argument to the schema. .. code-block:: python class BlogSchema2(Schema): title = fields.String() author = fields.Nested(UserSchema(only=("email",))) schema = BlogSchema2() result = schema.dump(blog) pprint(result) # { # 'title': u'Something Completely Different', # 'author': {'email': u'monty@python.org'} # } Dotted paths may be passed to ``only`` and ``exclude`` to specify nested attributes. .. code-block:: python class SiteSchema(Schema): blog = fields.Nested(BlogSchema2) schema = SiteSchema(only=("blog.author.email",)) result = schema.dump(site) pprint(result) # { # 'blog': { # 'author': {'email': u'monty@python.org'} # } # } You can replace nested data with a single value (or flat list of values if ``many=True``) using the :class:`Pluck ` field. .. code-block:: python class UserSchema(Schema): name = fields.String() email = fields.Email() friends = fields.Pluck("self", "name", many=True) # ... create ``user`` ... serialized_data = UserSchema().dump(user) pprint(serialized_data) # { # "name": "Steve", # "email": "steve@example.com", # "friends": ["Mike", "Joe"] # } deserialized_data = UserSchema().load(result) pprint(deserialized_data) # { # "name": "Steve", # "email": "steve@example.com", # "friends": [{"name": "Mike"}, {"name": "Joe"}] # } .. _partial-loading: Partial loading --------------- Nested schemas also inherit the ``partial`` parameter of the parent ``load`` call. .. code-block:: python class UserSchemaStrict(Schema): name = fields.String(required=True) email = fields.Email() created_at = fields.DateTime(required=True) class BlogSchemaStrict(Schema): title = fields.String(required=True) author = fields.Nested(UserSchemaStrict, required=True) schema = BlogSchemaStrict() blog = {"title": "Something Completely Different", "author": {}} result = schema.load(blog, partial=True) pprint(result) # {'author': {}, 'title': 'Something Completely Different'} You can specify a subset of the fields to allow partial loading using dot delimiters. .. code-block:: python author = {"name": "Monty"} blog = {"title": "Something Completely Different", "author": author} result = schema.load(blog, partial=("title", "author.created_at")) pprint(result) # {'author': {'name': 'Monty'}, 'title': 'Something Completely Different'} .. _two-way-nesting: Two-way nesting --------------- If you have two objects that nest each other, you can pass a callable to `Nested `. This allows you to resolve order-of-declaration issues, such as when one schema nests a schema that is declared below it. For example, a representation of an ``Author`` model might include the books that have a many-to-one relationship to it. Correspondingly, a representation of a ``Book`` will include its author representation. .. code-block:: python class BookSchema(Schema): id = fields.Int(dump_only=True) title = fields.Str() # Make sure to use the 'only' or 'exclude' # to avoid infinite recursion author = fields.Nested(lambda: AuthorSchema(only=("id", "title"))) class AuthorSchema(Schema): id = fields.Int(dump_only=True) title = fields.Str() books = fields.List(fields.Nested(BookSchema(exclude=("author",)))) .. code-block:: python from marshmallow import pprint from mymodels import Author, Book author = Author(name="William Faulkner") book = Book(title="As I Lay Dying", author=author) book_result = BookSchema().dump(book) pprint(book_result, indent=2) # { # "id": 124, # "title": "As I Lay Dying", # "author": { # "id": 8, # "name": "William Faulkner" # } # } author_result = AuthorSchema().dump(author) pprint(author_result, indent=2) # { # "id": 8, # "name": "William Faulkner", # "books": [ # { # "id": 124, # "title": "As I Lay Dying" # } # ] # } You can also pass a class name as a string to `Nested `. This is useful for avoiding circular imports when your schemas are located in different modules. .. code-block:: python # books.py from marshmallow import Schema, fields class BookSchema(Schema): id = fields.Int(dump_only=True) title = fields.Str() author = fields.Nested("AuthorSchema", only=("id", "title")) .. code-block:: python # authors.py from marshmallow import Schema, fields class AuthorSchema(Schema): id = fields.Int(dump_only=True) title = fields.Str() books = fields.List(fields.Nested("BookSchema", exclude=("author",))) .. note:: If you have multiple schemas with the same class name, you must pass the full, module-qualified path. :: author = fields.Nested("authors.BookSchema", only=("id", "title")) .. _self-nesting: Nesting a schema within itself ------------------------------ If the object to be marshalled has a relationship to an object of the same type, you can nest the `Schema ` within itself by passing a callable that returns an instance of the same schema. .. code-block:: python class UserSchema(Schema): name = fields.String() email = fields.Email() # Use the 'exclude' argument to avoid infinite recursion employer = fields.Nested(lambda: UserSchema(exclude=("employer",))) friends = fields.List(fields.Nested(lambda: UserSchema())) user = User("Steve", "steve@example.com") user.friends.append(User("Mike", "mike@example.com")) user.friends.append(User("Joe", "joe@example.com")) user.employer = User("Dirk", "dirk@example.com") result = UserSchema().dump(user) pprint(result, indent=2) # { # "name": "Steve", # "email": "steve@example.com", # "friends": [ # { # "name": "Mike", # "email": "mike@example.com", # "friends": [], # "employer": null # }, # { # "name": "Joe", # "email": "joe@example.com", # "friends": [], # "employer": null # } # ], # "employer": { # "name": "Dirk", # "email": "dirk@example.com", # "friends": [] # } # } Next steps ---------- - Want to create your own field type? See the :doc:`custom_fields` page. - Need to add schema-level validation, post-processing, or error handling behavior? See the :doc:`extending/index` page. - For more detailed usage examples, check out the :doc:`examples/index` page. marshmallow-3.26.1/docs/quickstart.rst000066400000000000000000000452701475016050200200150ustar00rootroot00000000000000Quickstart ========== This guide will walk you through the basics of creating schemas for serializing and deserializing data. Declaring schemas ----------------- Let's start with a basic user "model". .. code-block:: python from dataclasses import dataclass, field import datetime as dt @dataclass class User: name: str email: str created_at: dt.datetime = field(default_factory=dt.datetime.now) Create a schema by defining a class with variables mapping attribute names to :class:`Field ` objects. .. code-block:: python from marshmallow import Schema, fields class UserSchema(Schema): name = fields.Str() email = fields.Email() created_at = fields.DateTime() .. seealso:: For a full reference on the available field classes, see the `fields module documentation `. .. admonition:: Creating schemas from dictionaries You can also create a schema from a dictionary of fields using the `from_dict ` method. .. code-block:: python from marshmallow import Schema, fields UserSchema = Schema.from_dict( { "name": fields.Str(), "email": fields.Email(), "created_at": fields.DateTime(), } ) `from_dict ` is especially useful for generating schemas at runtime. Serializing objects ("dumping") ------------------------------- Serialize objects by passing them to your schema's :meth:`dump ` method, which returns the formatted result. .. code-block:: python from pprint import pprint user = User(name="Monty", email="monty@python.org") schema = UserSchema() result = schema.dump(user) pprint(result) # {"name": "Monty", # "email": "monty@python.org", # "created_at": "2014-08-17T14:54:16.049594+00:00"} You can also serialize to a JSON-encoded string using :meth:`dumps `. .. code-block:: python json_result = schema.dumps(user) print(json_result) # '{"name": "Monty", "email": "monty@python.org", "created_at": "2014-08-17T14:54:16.049594+00:00"}' Filtering output ---------------- You may not need to output all declared fields every time you use a schema. You can specify which fields to output with the ``only`` parameter. .. code-block:: python summary_schema = UserSchema(only=("name", "email")) summary_schema.dump(user) # {"name": "Monty", "email": "monty@python.org"} You can also exclude fields by passing in the ``exclude`` parameter. Deserializing objects ("loading") --------------------------------- The reverse of the `dump ` method is `load `, which validates and deserializes an input dictionary to an application-level data structure. By default, :meth:`load ` will return a dictionary of field names mapped to deserialized values (or raise a :exc:`ValidationError ` with a dictionary of validation errors, which we'll :ref:`revisit later `). .. code-block:: python from pprint import pprint user_data = { "created_at": "2014-08-11T05:26:03.869245", "email": "ken@yahoo.com", "name": "Ken", } schema = UserSchema() result = schema.load(user_data) pprint(result) # {'name': 'Ken', # 'email': 'ken@yahoo.com', # 'created_at': datetime.datetime(2014, 8, 11, 5, 26, 3, 869245)}, Notice that the datetime string was converted to a `datetime` object. Deserializing to objects ++++++++++++++++++++++++ In order to deserialize to an object, define a method of your :class:`Schema` and decorate it with `post_load `. The method receives a dictionary of deserialized data. .. code-block:: python from marshmallow import Schema, fields, post_load class UserSchema(Schema): name = fields.Str() email = fields.Email() created_at = fields.DateTime() @post_load def make_user(self, data, **kwargs): return User(**data) Now, the `load ` method return a ``User`` instance. .. code-block:: python user_data = {"name": "Ronnie", "email": "ronnie@stones.com"} schema = UserSchema() result = schema.load(user_data) print(result) # => Handling collections of objects ------------------------------- Set ``many=True`` when dealing with iterable collections of objects. .. code-block:: python user1 = User(name="Mick", email="mick@stones.com") user2 = User(name="Keith", email="keith@stones.com") users = [user1, user2] schema = UserSchema(many=True) result = schema.dump(users) # OR UserSchema().dump(users, many=True) pprint(result) # [{'name': u'Mick', # 'email': u'mick@stones.com', # 'created_at': '2014-08-17T14:58:57.600623+00:00'} # {'name': u'Keith', # 'email': u'keith@stones.com', # 'created_at': '2014-08-17T14:58:57.600623+00:00'}] .. _validation: Validation ---------- `Schema.load ` (and its JSON-decoding counterpart, `Schema.loads `) raises a :exc:`ValidationError ` error when invalid data are passed in. You can access the dictionary of validation errors from the `ValidationError.messages ` attribute. The data that were correctly deserialized are accessible in `ValidationError.valid_data `. Some fields, such as the :class:`Email ` and :class:`URL ` fields, have built-in validation. .. code-block:: python from marshmallow import ValidationError try: result = UserSchema().load({"name": "John", "email": "foo"}) except ValidationError as err: print(err.messages) # => {"email": ['"foo" is not a valid email address.']} print(err.valid_data) # => {"name": "John"} When validating a collection, the errors dictionary will be keyed on the indices of invalid items. .. code-block:: python from pprint import pprint from marshmallow import Schema, fields, ValidationError class BandMemberSchema(Schema): name = fields.String(required=True) email = fields.Email() user_data = [ {"email": "mick@stones.com", "name": "Mick"}, {"email": "invalid", "name": "Invalid"}, # invalid email {"email": "keith@stones.com", "name": "Keith"}, {"email": "charlie@stones.com"}, # missing "name" ] try: BandMemberSchema(many=True).load(user_data) except ValidationError as err: pprint(err.messages) # {1: {'email': ['Not a valid email address.']}, # 3: {'name': ['Missing data for required field.']}} You can perform additional validation for a field by passing the ``validate`` argument. There are a number of built-in validators in the :ref:`marshmallow.validate ` module. .. code-block:: python from pprint import pprint from marshmallow import Schema, fields, validate, ValidationError class UserSchema(Schema): name = fields.Str(validate=validate.Length(min=1)) permission = fields.Str(validate=validate.OneOf(["read", "write", "admin"])) age = fields.Int(validate=validate.Range(min=18, max=40)) in_data = {"name": "", "permission": "invalid", "age": 71} try: UserSchema().load(in_data) except ValidationError as err: pprint(err.messages) # {'age': ['Must be greater than or equal to 18 and less than or equal to 40.'], # 'name': ['Shorter than minimum length 1.'], # 'permission': ['Must be one of: read, write, admin.']} You may implement your own validators. A validator is a callable that accepts a single argument, the value to validate. If validation fails, the callable should raise a :exc:`ValidationError ` with a useful error message or return ``False`` (for a generic error message). .. code-block:: python from marshmallow import Schema, fields, ValidationError def validate_quantity(n): if n < 0: raise ValidationError("Quantity must be greater than 0.") if n > 30: raise ValidationError("Quantity must not be greater than 30.") class ItemSchema(Schema): quantity = fields.Integer(validate=validate_quantity) in_data = {"quantity": 31} try: result = ItemSchema().load(in_data) except ValidationError as err: print(err.messages) # => {'quantity': ['Quantity must not be greater than 30.']} You may also pass a collection (list, tuple, generator) of callables to ``validate``. .. warning:: Validation occurs on deserialization but not on serialization. To improve serialization performance, data passed to `Schema.dump ` are considered valid. .. seealso:: You can register a custom error handler function for a schema by overriding the :func:`handle_error ` method. See the :doc:`extending/custom_error_handling` page for more info. .. seealso:: If you need to validate multiple fields within a single validator, see :ref:`schema_validation`. Field validators as methods +++++++++++++++++++++++++++ It is sometimes convenient to write validators as methods. Use the `validates ` decorator to register field validator methods. .. code-block:: python from marshmallow import fields, Schema, validates, ValidationError class ItemSchema(Schema): quantity = fields.Integer() @validates("quantity") def validate_quantity(self, value): if value < 0: raise ValidationError("Quantity must be greater than 0.") if value > 30: raise ValidationError("Quantity must not be greater than 30.") Required fields --------------- Make a field required by passing ``required=True``. An error will be raised if the the value is missing from the input to `Schema.load `. To customize the error message for required fields, pass a `dict` with a ``required`` key as the ``error_messages`` argument for the field. .. code-block:: python from pprint import pprint from marshmallow import Schema, fields, ValidationError class UserSchema(Schema): name = fields.String(required=True) age = fields.Integer(required=True, error_messages={"required": "Age is required."}) city = fields.String( required=True, error_messages={"required": {"message": "City required", "code": 400}}, ) email = fields.Email() try: result = UserSchema().load({"email": "foo@bar.com"}) except ValidationError as err: pprint(err.messages) # {'age': ['Age is required.'], # 'city': {'code': 400, 'message': 'City required'}, # 'name': ['Missing data for required field.']} Partial loading --------------- When using the same schema in multiple places, you may only want to skip ``required`` validation by passing ``partial``. .. code-block:: python class UserSchema(Schema): name = fields.String(required=True) age = fields.Integer(required=True) result = UserSchema().load({"age": 42}, partial=("name",)) # OR UserSchema(partial=('name',)).load({'age': 42}) print(result) # => {'age': 42} You can ignore missing fields entirely by setting ``partial=True``. .. code-block:: python class UserSchema(Schema): name = fields.String(required=True) age = fields.Integer(required=True) result = UserSchema().load({"age": 42}, partial=True) # OR UserSchema(partial=True).load({'age': 42}) print(result) # => {'age': 42} Specifying defaults ------------------- `load_default` specifies the default deserialization value for a field. Likewise, `dump_default` specifies the default serialization value. .. code-block:: python class UserSchema(Schema): id = fields.UUID(load_default=uuid.uuid1) birthdate = fields.DateTime(dump_default=dt.datetime(2017, 9, 29)) UserSchema().load({}) # {'id': UUID('337d946c-32cd-11e8-b475-0022192ed31b')} UserSchema().dump({}) # {'birthdate': '2017-09-29T00:00:00+00:00'} .. _unknown: Handling unknown fields ----------------------- By default, :meth:`load ` will raise a :exc:`ValidationError ` if it encounters a key with no matching ``Field`` in the schema. .. code-block:: python from marshmallow import Schema, fields class UserSchema(Schema): name = fields.Str() email = fields.Email() created_at = fields.DateTime() UserSchema().load( { "name": "Monty", "email": "monty@python.org", "created_at": "2014-08-17T14:54:16.049594+00:00", "extra": "Not a field", } ) # raises marshmallow.exceptions.ValidationError: {'extra': ['Unknown field.']} This behavior can be modified with the ``unknown`` option, which accepts one of the following: - `RAISE ` (default): raise a :exc:`ValidationError ` if there are any unknown fields - `EXCLUDE `: exclude unknown fields - `INCLUDE `: accept and include the unknown fields You can specify `unknown ` in the `class Meta ` of your `Schema `, .. code-block:: python from pprint import pprint from marshmallow import Schema, fields, INCLUDE class UserSchema(Schema): name = fields.Str() email = fields.Email() created_at = fields.DateTime() class Meta: unknown = INCLUDE result = UserSchema().load( { "name": "Monty", "email": "monty@python.org", "created_at": "2014-08-17T14:54:16.049594+00:00", "extra": "Not a field", } ) pprint(result) # {'created_at': datetime.datetime(2014, 8, 17, 14, 54, 16, 49594, tzinfo=datetime.timezone(datetime.timedelta(0), '+0000')), # 'email': 'monty@python.org', # 'extra': 'Not a field', # 'name': 'Monty'} at instantiation time, .. code-block:: python schema = UserSchema(unknown=INCLUDE) or when calling :meth:`load `. .. code-block:: python UserSchema().load(data, unknown=INCLUDE) The `unknown ` option value set in `load ` will override the value applied at instantiation time, which itself will override the value defined in the `class Meta `. This order of precedence allows you to change the behavior of a schema for different contexts. Validation without deserialization ---------------------------------- If you only need to validate input data (without deserializing to an object), you can use `Schema.validate `. .. code-block:: python errors = UserSchema().validate({"name": "Ronnie", "email": "invalid-email"}) print(errors) # {'email': ['Not a valid email address.']} "Read-only" and "write-only" fields ----------------------------------- In the context of a web API, the ``dump_only`` and ``load_only`` parameters are conceptually equivalent to "read-only" and "write-only" fields, respectively. .. code-block:: python class UserSchema(Schema): name = fields.Str() # password is "write-only" password = fields.Str(load_only=True) # created_at is "read-only" created_at = fields.DateTime(dump_only=True) .. warning:: When loading, dump-only fields are considered unknown. If the ``unknown`` option is set to ``INCLUDE``, values with keys corresponding to those fields are therefore loaded with no validation. Specifying serialization/deserialization keys --------------------------------------------- Schemas will (de)serialize an input dictionary from/to an output dictionary whose keys are identical to the field names. If you are consuming and producing data that does not match your schema, you can specify the output keys via the `data_key` argument. .. code-block:: python class UserSchema(Schema): name = fields.String() email = fields.Email(data_key="emailAddress") s = UserSchema() data = {"name": "Mike", "email": "foo@bar.com"} result = s.dump(data) # {'name': u'Mike', # 'emailAddress': 'foo@bar.com'} data = {"name": "Mike", "emailAddress": "foo@bar.com"} result = s.load(data) # {'name': u'Mike', # 'email': 'foo@bar.com'} .. _meta_options: Implicit field creation ----------------------- .. warning:: Implicit field creation is deprecated and is removed in marshmallow 4. Fields should be declared explicitly. .. code-block:: python # 3.x class UserSchema(Schema): class Meta: fields = ("name", "birthdate") # 4.x class UserSchema(Schema): name = fields.String() email = fields.Date() When your model has many attributes, specifying the field type for every attribute can get repetitive, especially when many of the attributes are already native Python datatypes. The ``fields`` option allows you to specify implicitly-created fields. marshmallow will choose an appropriate field type based on the attribute's type. Let's refactor our User schema to be more concise. .. code-block:: python class UserSchema(Schema): uppername = fields.Function(lambda obj: obj.name.upper()) class Meta: fields = ("name", "email", "created_at", "uppername") Note that ``name`` will be automatically formatted as a :class:`String ` and ``created_at`` will be formatted as a :class:`DateTime `. .. note:: If instead you want to specify which field names to include *in addition* to the explicitly declared fields, you can use the ``additional`` option. The schema below is equivalent to above: .. code-block:: python class UserSchema(Schema): uppername = fields.Function(lambda obj: obj.name.upper()) class Meta: # No need to include 'uppername' additional = ("name", "email", "created_at") Next steps ---------- - Need to represent relationships between objects? See the :doc:`nesting` page. - Want to create your own field type? See the :doc:`custom_fields` page. - Need to add schema-level validation, post-processing, or error handling behavior? See the :doc:`extending/index` page. - For more detailed usage examples, check out the :doc:`examples/index` page. marshmallow-3.26.1/docs/top_level.rst000066400000000000000000000003011475016050200175760ustar00rootroot00000000000000Top-level API ============= .. automodule:: marshmallow :members: :autosummary: :exclude-members: OPTIONS_CLASS .. data:: EXCLUDE .. data:: INCLUDE .. data:: RAISE .. data:: missing marshmallow-3.26.1/docs/upgrading.rst000066400000000000000000001604661475016050200176100ustar00rootroot00000000000000Upgrading to newer releases =========================== This section documents migration paths to new releases. Upgrading to 3.26 +++++++++++++++++ ``ordered`` is deprecated ************************* The `ordered ` class Meta option is removed, since order is already preserved by default. .. code-block:: python from marshmallow import Schema, fields # <3.26 class MySchema(Schema): id = fields.Integer() class Meta: ordered = True # >=3.26 class MySchema(Schema): id = fields.Integer() .. note:: You can set `marshmallow.Schema.dict_class` to `collections.OrderedDict` to force the output type of `marshmallow.Schema.dump` to be an `OrderedDict `. Upgrading to 3.24 +++++++++++++++++ ``Field`` usage *************** `Field ` is the base class for all fields and should not be used directly within schemas. Only use subclasses of `Field ` in your schemas. Instantiating `Field ` will raise a warning in marshmallow>=3.24 and an error in marshmallow 4.0. .. code-block:: python from marshmallow import Schema, fields # <3.24 class UserSchema(Schema): name = fields.Field() # >=3.24 class UserSchema(Schema): name = fields.String() ``Number`` and ``Mapping`` fields as base classes ************************************************* `Number ` and `Mapping ` are bases classes that should not be used within schemas. Use their subclasses instead. Instantiating `Number ` or `Mapping ` will raise a warning in marshmallow>=3.24 and an error in marshmallow 4.0. .. code-block:: python from marshmallow import Schema, fields # <3.24 class PackageSchema(Schema): revision = fields.Number() dependencies = fields.Mapping() # >=3.24 class PackageSchema(Schema): revision = fields.Integer() dependencies = fields.Dict() Validators must raise a :exc:`ValidationError ` *************************************************************************************** Validators must raise a :exc:`ValidationError ` when the value is invalid. Returning `False` from a validator is deprecated and will be removed in marshmallow 4.0. .. code-block:: python from marshmallow import Schema, fields # <3.24 class UserSchema(Schema): password = fields.String(validate=lambda x: x == "password") # >=3.24 def validate_password(val): if val != "password": raise ValidationError("Invalid password.") class UserSchema(Schema): password = fields.String(validate=validate_password) If you want to use anonymous functions, you can use this helper function in your code. .. code-block:: python from typing import Any, Callable from marshmallow import Schema, fields def predicate( func: Callable[[Any], bool], ) -> Callable[[Any], None]: def validate(value: Any) -> None: if func(value) is False: raise ValidationError("Invalid value.") return validate # Usage class UserSchema(Schema): password = fields.String(validate=predicate(lambda x: x == "password")) ``context`` is deprecated ************************* Passing ``context`` to `Schema ` classes will raise a warning in marshmallow>=3.24 and will be removed in marshmallow 4.0. Use `contextvars.ContextVar` for passing context to fields, :doc:`pre-/post-processing methods `, and :doc:`validators ` instead. Upgrading to 3.3 ++++++++++++++++ In 3.3, `fields.Nested ` may take a callable that returns a schema instance. Use this to resolve order-of-declaration issues when schemas nest each other. .. code-block:: python from marshmallow import Schema, fields # <3.3 class AlbumSchema(Schema): title = fields.Str() artist = fields.Nested("ArtistSchema", only=("name",)) class ArtistSchema(Schema): name = fields.Str() albums = fields.List(fields.Nested(AlbumSchema)) # >=3.3 class AlbumSchema(Schema): title = fields.Str() artist = fields.Nested(lambda: ArtistSchema(only=("name",))) class ArtistSchema(Schema): name = fields.Str() albums = fields.List(fields.Nested(AlbumSchema)) A callable should also be used when nesting a schema within itself. Passing ``"self"`` is deprecated. .. code-block:: python from marshmallow import Schema, fields # <3.3 class PersonSchema(Schema): partner = fields.Nested("self", exclude=("partner",)) friends = fields.List(fields.Nested("self")) # >=3.3 class PersonSchema(Schema): partner = fields.Nested(lambda: PersonSchema(exclude=("partner"))) friends = fields.List(fields.Nested(lambda: PersonSchema())) .. _upgrading_3_0: Upgrading to 3.0 ++++++++++++++++ Python compatibility ******************** The marshmallow 3.x series requires Python 3. Schemas are always strict ************************* Two major changes were made to (de)serialization behavior: - The ``strict`` parameter was removed. Schemas are always strict. - `Schema().load ` and `Schema().dump ` don't return a ``(data, errors)`` tuple any more. Only ``data`` is returned. If invalid data are passed, a :exc:`ValidationError ` is raised. The dictionary of validation errors is accessible from the `ValidationError.messages ` attribute, along with the valid data from the `ValidationError.valid_data ` attribute. .. code-block:: python from marshmallow import ValidationError # 2.x schema = UserSchema() data, errors = schema.load({"name": "Monty", "email": "monty@python.org"}) # OR schema = UserSchema(strict=True) try: data, _ = schema.load({"name": "Monty", "email": "monty@python.org"}) except ValidationError as err: errors = err.messages valid_data = err.valid_data # 3.x schema = UserSchema() # There is only one right way try: data = schema.load({"name": "Monty", "email": "monty@python.org"}) except ValidationError as err: errors = err.messages valid_data = err.valid_data `Schema.validate ` always returns a dictionary of validation errors (same as 2.x with ``strict=False``). .. code-block:: python schema.validate({"email": "invalid"}) # {'email': ['Not a valid email address.']} Setting the ``strict`` option on `class Meta ` has no effect on `Schema ` behavior. Passing ``strict=True`` or ``strict=False`` to the `Schema ` constructor will raise a :exc:`TypeError`. .. code-block:: python # 3.x UserSchema(strict=True) # TypeError: __init__() got an unexpected keyword argument 'strict' .. seealso:: See GitHub issues :issue:`377` and :issue:`598` for the discussions on this change. Decorated methods and ``handle_error`` receive ``many`` and ``partial`` *********************************************************************** Methods decorated with `pre_load `, `post_load `, `pre_dump `, `post_dump `, and `validates_schema ` receive ``many`` as a keyword argument. In addition, `pre_load `, `post_load `, and `validates_schema ` receive ``partial``. To account for these additional arguments, add ``**kwargs`` to your methods. .. code-block:: python # 2.x class UserSchema(Schema): name = fields.Str() slug = fields.Str() @pre_load def slugify_name(self, in_data): in_data["slug"] = in_data["slug"].lower().strip().replace(" ", "-") return in_data # 3.x class UserSchema(Schema): name = fields.Str() slug = fields.Str() @pre_load def slugify_name(self, in_data, **kwargs): in_data["slug"] = in_data["slug"].lower().strip().replace(" ", "-") return in_data `Schema.handle_error ` also receives ``many`` and ``partial`` as keyword arguments. .. code-block:: python # 2.x class UserSchema(Schema): def handle_error(self, exc, data): raise AppError("An error occurred with input: {0}".format(data)) # 3.x class UserSchema(Schema): def handle_error(self, exc, data, **kwargs): raise AppError("An error occurred with input: {0}".format(data)) Validation does not occur on serialization ****************************************** `Schema.dump ` will no longer validate and collect error messages. You *must* validate your data before serializing it. .. code-block:: python from marshmallow import Schema, fields, ValidationError invalid_data = dict(created_at="invalid") class WidgetSchema(Schema): created_at = fields.DateTime() # 2.x WidgetSchema(strict=True).dump(invalid_data) # marshmallow.exceptions.ValidationError: {'created_at': ['"invalid" cannot be formatted as a datetime.']} # 3.x WidgetSchema().dump(invalid_data) # AttributeError: 'str' object has no attribute 'isoformat' # Instead, validate before dumping schema = WidgetSchema() try: widget = schema.load(invalid_data) except ValidationError: print("handle errors...") else: dumped = schema.dump(widget) Deserializing invalid types raises a ``ValidationError`` ******************************************************** Numbers, booleans, strings, and ``None`` are considered invalid input to `Schema.load `. .. code-block:: python # 2.x # Passes silently schema.load(None) schema.load(False) schema.load("pass") # 3.x # marshmallow.exceptions.ValidationError: {'_schema': ['Invalid input type.']} schema.load(None) schema.load(False) schema.load("nope") When ``many=True``, non-collection types are also considered invalid. .. code-block:: python # 2.x # Passes silently schema.load(None, many=True) schema.load({}, many=True) schema.load("pass", many=True) # 3.x # marshmallow.exceptions.ValidationError: {'_schema': ['Invalid input type.']} schema.load(None, many=True) schema.load({}, many=True) schema.load("invalid", many=True) ``ValidationError.fields`` is removed ************************************* :exc:`ValidationError ` no longer stores a list of `Field ` instances associated with the validation errors. If you need field instances associated with an error, you can access them from ``schema.fields``. .. code-block:: python from marshmallow import Schema, fields, ValidationError class MySchema(Schema): foo = fields.Int() schema = MySchema() try: schema.load({"foo": "invalid"}) except ValidationError as error: field = schema.fields["foo"] # ... ``ValidationError`` expects a single field name *********************************************** :exc:`ValidationError ` no longer accepts a list of field names. It expects a single field name. If none is passed, the error refers to the schema. To return an error for several fields at once, a `dict` must be used. .. code-block:: python from marshmallow import Schema, fields, validates_schema, ValidationError class NumberSchema(Schema): field_a = fields.Integer() field_b = fields.Integer() # 2.x @validates_schema def validate_numbers(self, data): if data["field_b"] >= data["field_a"]: raise ValidationError( "field_a must be greater than field_b", ["field_a", "field_b"] ) # 3.x @validates_schema def validate_numbers(self, data): if data["field_b"] >= data["field_a"]: raise ValidationError( { "field_a": ["field_a must be greater than field_b"], "field_b": ["field_a must be greater than field_b"], } ) ``ValidationError`` error messages are deep-merged ************************************************** When multiple :exc:`ValidationError ` are raised, the error structures are merged in the final :exc:`ValidationError` raised at the end of the process. When reporting error messages as `dict`, the keys should refer to subitems of the item the message refers to, and the values should be error messages. See :doc:`extending/schema_validation` for an example. page for an example. Schemas raise ``ValidationError`` when deserializing data with unknown keys *************************************************************************** marshmallow 3.x schemas can deal with unknown keys in three different ways, configurable with the ``unknown`` option: - ``EXCLUDE``: drop those keys (same as marshmallow 2) - ``INCLUDE``: pass those keys/values as is, with no validation performed - ``RAISE`` (default): raise a ``ValidationError`` The ``unknown`` option can be passed as a Meta option, on Schema instantiation, or at load time. .. code-block:: python from marshmallow import Schema, fields, EXCLUDE, INCLUDE, RAISE class MySchema(Schema): foo = fields.Int() class Meta: # Pass EXCLUDE as Meta option to keep marshmallow 2 behavior unknown = EXCLUDE MySchema().load({"foo": 42, "bar": "whatever"}) # => ['foo': 42] #  Value passed on instantiation overrides Meta option schema = MySchema(unknown=INCLUDE) schema.load({"foo": 42, "bar": "whatever"}) # => ['foo': 42, 'bar': 'whatever'] #  Value passed on load overrides instance attribute schema.load({"foo": 42, "bar": "whatever"}, unknown=RAISE) # => ValidationError Overriding ``get_attribute`` **************************** If your `Schema ` overrides `get_attribute `, you will need to update the method's signature. The positions of the ``attr`` and ``obj`` arguments were switched for consistency with Python builtins, e.g. `getattr`. .. code-block:: python from marshmallow import Schema # 2.x class MySchema(Schema): def get_attribute(self, attr, obj, default): return getattr(obj, attr, default) # 3.x class MySchema(Schema): def get_attribute(self, obj, attr, default): return getattr(obj, attr, default) ``pass_original=True`` passes individual items when ``many=True`` ***************************************************************** When ``pass_original=True`` is passed to `validates_schema `, `post_load `, or `post_dump `, the `original_data` argument will be a single item corresponding to the (de)serialized datum. .. code-block:: python from marshmallow import Schema, fields, post_load, EXCLUDE class ShoeSchema(Schema): size = fields.Int() class Meta: unknown = EXCLUDE @post_load(pass_original=True) def post_load(self, data, original_data, **kwargs): # original_data has 'width' but # data does not because it's not # in the schema assert "width" in original_data assert "width" not in data return data input_data = [{"size": 10, "width": "M"}, {"size": 6, "width": "W"}] print(ShoeSchema(many=True).load(input_data)) # [{'size': 10}, {'size': 6}] ``utils.get_func_args`` no longer returns bound arguments ********************************************************* The `utils.get_func_args ` function will no longer return bound arguments, e.g. `'self'`. .. code-block:: python from marshmallow.utils import get_func_args class MyCallable: def __call__(self, foo, bar): return 42 callable_obj = MyCallable() # 2.x get_func_args(callable_obj) # => ['self', 'foo', 'bar'] # 3.x get_func_args(callable_obj) # => ['foo', 'bar'] Handling ``AttributeError`` in ``Method`` and ``Function`` fields ***************************************************************** The `Method ` and `Function ` fields no longer swallow ``AttributeErrors``. Therefore, your methods and functions are responsible for handling inputs such as `None`. .. code-block:: python from marshmallow import Schema, fields, missing # 2.x class ShapeSchema(Schema): area = fields.Method("get_area") def get_area(self, obj): return obj.height * obj.length schema = ShapeSchema() # In 2.x, the following would pass without errors # In 3.x, and AttributeError would be raised result = schema.dump(None) result # => {} # 3.x class ShapeSchema(Schema): area = fields.Method("get_area") def get_area(self, obj): if obj is None: # 'area' will not appear in serialized output return missing return obj.height * obj.length schema = ShapeSchema() result = schema.dump(None) result # => {} Adding additional data to serialized output ******************************************* Use a `post_dump ` to add additional data on serialization. The ``extra`` argument on `Schema ` was removed. .. code-block:: python from marshmallow import Schema, fields, post_dump # 2.x class MySchema(Schema): x = fields.Int() y = fields.Int() schema = MySchema(extra={"z": 123}) schema.dump({"x": 1, "y": 2}) # => {'z': 123, 'y': 2, 'x': 1} # 3.x class MySchema(Schema): x = fields.Int() y = fields.Int() @post_dump def add_z(self, output): output["z"] = 123 return output schema = MySchema() schema.dump({"x": 1, "y": 2}) # => {'z': 123, 'y': 2, 'x': 1} Schema-level validators are skipped when field validation fails *************************************************************** By default, schema validator methods decorated by `validates_schema ` won't execute if any of the field validators fails (including ``required=True`` validation). .. code-block:: python from marshmallow import Schema, fields, validates_schema, ValidationError class MySchema(Schema): x = fields.Int(required=True) y = fields.Int(required=True) @validates_schema def validate_schema(self, data): if data["x"] <= data["y"]: raise ValidationError("x must be greater than y") schema = MySchema() # 2.x # A KeyError is raised in validate_schema schema.load({"x": 2}) # 3.x # marshmallow.exceptions.ValidationError: {'y': ['Missing data for required field.']} # validate_schema is not run schema.load({"x": 2}) If you want a schema validator to run even if a field validator fails, pass ``skip_on_field_errors=False``. Make sure your code handles cases where fields are missing from the deserialized data (due to validation errors). .. code-block:: python from marshmallow import Schema, fields, validates_schema, ValidationError class MySchema(Schema): x = fields.Int(required=True) y = fields.Int(required=True) @validates_schema(skip_on_field_errors=False) def validate_schema(self, data): if "x" in data and "y" in data: if data["x"] <= data["y"]: raise ValidationError("x must be greater than y") schema = MySchema() schema.load({"x": 2}) # marshmallow.exceptions.ValidationError: {'y': ['Missing data for required field.']} `SchemaOpts` constructor receives ``ordered`` argument ****************************************************** Subclasses of `SchemaOpts ` receive an additional argument, ``ordered``, which is `True` if the `ordered` option is set to `True` on a Schema or one of its parent classes. .. code-block:: python from marshmallow import SchemaOpts # 2.x class CustomOpts(SchemaOpts): def __init__(self, meta): super().__init__(meta) self.custom_option = getattr(meta, "meta", False) # 3.x class CustomOpts(SchemaOpts): def __init__(self, meta, ordered=False): super().__init__(meta, ordered) self.custom_option = getattr(meta, "meta", False) `ContainsOnly` accepts empty and duplicate values ************************************************* `validate.ContainsOnly ` now accepts duplicate values in the input value. .. code-block:: python from marshmallow import validate validator = validate.ContainsOnly(["red", "blue"]) # in 2.x the following raises a ValidationError # in 3.x, no error is raised validator(["red", "red", "blue"]) If you don't want to accept duplicates, use a custom validator, like the following. .. code-block:: python from marshmallow import ValidationError from marshmallow.validate import ContainsOnly class ContainsOnlyNoDuplicates(ContainsOnly): def __call__(self, value): ret = super(ContainsOnlyNoDuplicates, self).__call__(value) if len(set(value)) != len(value): raise ValidationError("Duplicate values not allowed") return ret .. note:: If you need to handle unhashable types, you can use the `implementation of ContainsOnly from marshmallow 2.x `_. `validate.ContainsOnly ` also accepts empty values as valid input. .. code-block:: python from marshmallow import validate validator = validate.ContainsOnly(["red", "blue"]) # in 2.x the following raises a ValidationError # in 3.x, no error is raised validator([]) To validate against empty inputs, use `validate.Length(min=1) `. ``json_module`` option is renamed to ``render_module`` ****************************************************** The ``json_module`` `class Meta ` option is deprecated in favor of ``render_module``. .. code-block:: python import ujson # 2.x class MySchema(Schema): class Meta: json_module = ujson # 3.x class MySchema(Schema): class Meta: render_module = ujson ``missing`` and ``default`` ``Field`` parameters are passed in deserialized form ******************************************************************************** .. code-block:: python # 2.x class UserSchema(Schema): id = fields.UUID(missing=lambda: str(uuid.uuid1())) birthdate = fields.DateTime(default=lambda: dt.datetime(2017, 9, 19).isoformat()) # 3.x class UserSchema(Schema): id = fields.UUID(missing=uuid.uuid1) birthdate = fields.DateTime(default=dt.datetime(2017, 9, 19)) Pass ``default`` as a keyword argument ************************************** `fields.Boolean ` now receives additional ``truthy`` and ``falsy`` parameters. Consequently, the ``default`` parameter should always be passed as a keyword argument. .. code-block:: python # 2.x fields.Boolean(True) # 3.x fields.Boolean(default=True) ``Email`` and ``URL`` fields do not validate on serialization ************************************************************* `fields.Email ` and `fields.URL ` only validate input upon deserialization. They do not validate on serialization. This makes them more consistent with the other fields and improves serialization performance. ``load_from`` and ``dump_to`` are merged into ``data_key`` ********************************************************** The same key is used for serialization and deserialization. .. code-block:: python # 2.x class UserSchema(Schema): email = fields.Email(load_from="CamelCasedEmail", dump_to="CamelCasedEmail") # 3.x class UserSchema(Schema): email = fields.Email(data_key="CamelCasedEmail") It is not possible to specify a different key for serialization and deserialization on the same field. This use case is covered by using two different `Schema `. .. code-block:: python from marshmallow import Schema, fields # 2.x class UserSchema(Schema): id = fields.Str() email = fields.Email(load_from="CamelCasedEmail", dump_to="snake_case_email") # 3.x class BaseUserSchema(Schema): id = fields.Str() class LoadUserSchema(BaseUserSchema): email = fields.Email(data_key="CamelCasedEmail") class DumpUserSchema(BaseUserSchema): email = fields.Email(data_key="snake_case_email") Also, when ``data_key`` is specified on a field, only ``data_key`` is checked in the input data. In marshmallow 2.x the field name is checked if ``load_from`` is missing from the input data. Pre/Post-processors must return modified data ********************************************* In marshmallow 2.x, ``None`` returned by a pre or post-processor is interpreted as "the data was mutated". In marshmallow 3.x, the return value is considered as processed data even if it is ``None``. Processors that mutate the data should be updated to also return it. .. code-block:: python # 2.x class UserSchema(Schema): name = fields.Str() slug = fields.Str() @pre_load def slugify_name(self, in_data): # In 2.x, implicitly returning None implied that data were mutated in_data["slug"] = in_data["slug"].lower().strip().replace(" ", "-") # 3.x class UserSchema(Schema): name = fields.Str() slug = fields.Str() @pre_load def slugify_name(self, in_data, **kwargs): # In 3.x, always return the processed data in_data["slug"] = in_data["slug"].lower().strip().replace(" ", "-") return in_data ``Nested`` field no longer supports plucking ******************************************** In marshmallow 2.x, when a string was passed to a ``Nested`` field's ```only`` parameter, the field would be plucked. In marshmallow 3.x, the ``Pluck`` field must be used instead. .. code-block:: python # 2.x class UserSchema(Schema): name = fields.Str() friends = fields.Nested("self", many=True, only="name") # 3.x class UserSchema(Schema): name = fields.Str() friends = fields.Pluck("self", "name", many=True) Accessing attributes on objects within a list ********************************************* In order to serialize attributes on inner objects within a list, use the ``Pluck`` field. .. code-block:: python # 2.x class FactorySchema(Schema): widget_ids = fields.List(fields.Int(attribute="id")) # 3.x class FactorySchema(Schema): widget_ids = fields.List(fields.Pluck(WidgetSchema, "id")) ``List`` does not wrap single values in a list on serialization *************************************************************** In marshmallow 2.x, ``List`` serializes a single object as a list with a single element. In marshmallow 3.x, the object is assumed to be iterable and passing a non-iterable element results in an error. .. code-block:: python class UserSchema(Schema): numbers = fields.List(fields.Int()) user = {"numbers": 1} UserSchema().dump(user) # 2.x # => {'numbers': [1]} # 3.x # => TypeError: 'int' object is not iterable ``Float`` field takes a new ``allow_nan`` parameter *************************************************** In marshmallow 2.x, ``Float`` field would serialize and deserialize special values such as ``nan``, ``inf`` or ``-inf``. In marshmallow 3, those values trigger a ``ValidationError`` unless ``allow_nan`` is ``True``. ``allow_nan`` defaults to ``False``. .. code-block:: python # 2.x class MySchema(Schema): x = fields.Float() MySchema().load({"x": "nan"}) # => {{'x': nan}} # 3.x class MySchema(Schema): x = fields.Float() y = fields.Float(allow_nan=True) MySchema().load({"x": 12, "y": "nan"}) # => {{'x': 12.0, 'y': nan}} MySchema().load({"x": "nan"}) # marshmallow.exceptions.ValidationError: {'x': ['Special numeric values (nan or infinity) are not permitted.']} ``DateTime`` field ``dateformat`` ``Meta`` option is renamed ``datetimeformat`` ******************************************************************************* The ``Meta`` option ``dateformat`` used to pass format to `DateTime ` field is renamed as ``datetimeformat``. `Date ` field gets a new ``format`` parameter to specify the format to use for serialization. ``dateformat`` ``Meta`` option now applies to `Date ` field. .. code-block:: python # 2.x class MySchema(Schema): x = fields.DateTime() class Meta: dateformat = "%Y-%m" MySchema().dump({"x": dt.datetime(2017, 9, 19)}) # => {{'x': '2017-09'}} # 3.x class MySchema(Schema): x = fields.DateTime() y = fields.Date() class Meta: datetimeformat = "%Y-%m" dateformat = "%m-%d" MySchema().dump({"x": dt.datetime(2017, 9, 19), "y": dt.date(2017, 9, 19)}) # => {{'x': '2017-09', 'y': '09-19'}} ``DateTime`` leaves timezone information untouched during serialization *********************************************************************** ``DateTime`` does not convert naive datetimes to UTC on serialization and ``LocalDateTime`` is removed. .. code-block:: python # 2.x class MySchema(Schema): x = fields.DateTime() y = fields.DateTime() z = fields.LocalDateTime() MySchema().dump( { "x": dt.datetime(2017, 9, 19), "y": dt.datetime(2017, 9, 19, tzinfo=dt.timezone(dt.timedelta(hours=2))), "z": dt.datetime(2017, 9, 19, tzinfo=dt.timezone(dt.timedelta(hours=2))), } ) # => {{'x': '2017-09-19T00:00:00+00:00', 'y': '2017-09-18T22:00:00+00:00', 'z': '2017-09-19T00:00:00+02:00'}} # 3.x class MySchema(Schema): x = fields.DateTime() y = fields.DateTime() MySchema().dump( { "x": dt.datetime(2017, 9, 19), "y": dt.datetime(2017, 9, 19, tzinfo=dt.timezone(dt.timedelta(hours=2))), } ) # => {{'x': '2017-09-19T00:00:00', 'y': '2017-09-19T00:00:00+02:00'}} The ``prefix`` ``Schema`` parameter is removed ********************************************** The ``prefix`` parameter of ``Schema`` is removed. The same feature can be achieved using a post_dump ` method. .. code-block:: python # 2.x class MySchema(Schema): f1 = fields.Raw() f2 = fields.Raw() MySchema(prefix="pre_").dump({"f1": "one", "f2": "two"}) # {'pre_f1': 'one', '_pre_f2': 'two'} # 3.x class MySchema(Schema): f1 = fields.Raw() f2 = fields.Raw() @post_dump def prefix_usr(self, data): return {"usr_{}".format(k): v for k, v in iteritems(data)} MySchema().dump({"f1": "one", "f2": "two"}) # {'pre_f1': 'one', '_pre_f2': 'two'} ``fields.FormattedString`` is removed ************************************* ``fields.FormattedString`` field is removed. Use `fields.Function ` or `fields.Method ` instead. .. code-block:: python # 2.x class MySchema(Schema): full_name = fields.FormattedString("{first_name} {last_name}") # 3.x class MySchema(Schema): full_name = fields.Function(lambda u: f"{u.first_name} {u.last_name}") ``attribute`` or ``data_key`` collision triggers an exception ************************************************************* When a `Schema ` is instantiated, a check is performed and a ``ValueError`` is triggered if - several fields have the same ``attribute`` value (or field name if ``attribute`` is not passed), excluding ``dump_only`` fields, or - several fields have the same ``data_key`` value (or field name if ``data_key`` is not passed), excluding ``load_only`` fields In marshmallow 2, it was possible to have multiple fields with the same ``attribute``. It would work provided the ``Schema`` was only used for dumping. When loading, the behaviour was undefined. In marshmallow 3, all but one of those fields must be marked as ``dump_only``. Likewise for ``data_key`` (formerly ``dump_to``) for fields that are not ``load_only``. .. code-block:: python # 2.x class MySchema(Schema): f1 = fields.Raw() f2 = fields.Raw(attribute="f1") f3 = fields.Raw(attribute="f5") f4 = fields.Raw(attribute="f5") MySchema() #  No error # 3.x class MySchema(Schema): f1 = fields.Raw() f2 = fields.Raw(attribute="f1") f3 = fields.Raw(attribute="f5") f4 = fields.Raw(attribute="f5") MySchema() # ValueError: 'Duplicate attributes: ['f1', 'f5]' class MySchema(Schema): f1 = fields.Raw() f2 = fields.Raw(attribute="f1", dump_only=True) f3 = fields.Raw(attribute="f5") f4 = fields.Raw(attribute="f5", dump_only=True) MySchema() # No error ``Field.fail`` is deprecated in favor of ``Field.make_error`` ************************************************************* `Field.fail ` is deprecated. Use `Field.make_error `. This allows you to re-raise exceptions using ``raise ... from ...``. .. code-block:: python from marshmallow import fields, ValidationError from packaging import version # 2.x class Version(fields.Field): default_error_messages = {"invalid": "Not a valid version."} def _deserialize(self, value, *args, **kwargs): try: return version.Version(value) except version.InvalidVersion: self.fail("invalid") # 3.x class Version(fields.Field): default_error_messages = {"invalid": "Not a valid version."} def _deserialize(self, value, *args, **kwargs): try: return version.Version(value) except version.InvalidVersion as error: raise self.make_error("invalid") from error ``python-dateutil`` recommended dependency is removed ***************************************************** In marshmallow 2, ``python-dateutil`` was used to deserialize RFC or ISO 8601 datetimes if it was installed. In marshmallow 3, datetime deserialization is done with no additional dependency. ``python-dateutil`` is no longer used by marshmallow. Custom Fields ************* To make your custom fields compatible with marshmallow 3, ``_deserialize`` should accept ``**kwargs``: .. code-block:: python from marshmallow import fields, ValidationError from packaging import version # 2.x class MyCustomField(fields.Field): def _deserialize(self, value, attr, obj): ... # 3.x class MyCustomField(fields.Field): def _deserialize(self, value, attr, obj, **kwargs): ... Upgrading to 2.3 ++++++++++++++++ The ``func`` parameter of `fields.Function ` was renamed to ``serialize``. .. code-block:: python # YES lowername = fields.Function(serialize=lambda obj: obj.name.lower()) # or lowername = fields.Function(lambda obj: obj.name.lower()) # NO lowername = fields.Function(func=lambda obj: obj.name.lower()) Similarly, the ``method_name`` of `fields.Method ` was also renamed to ``serialize``. .. code-block:: python # YES lowername = fields.Method(serialize="lowercase") # or lowername = fields.Method("lowercase") # NO lowername = fields.Method(method_name="lowercase") The ``func`` parameter is still available for backwards-compatibility. It will be removed in marshmallow 3.0. Both `fields.Function ` and `fields.Method ` will allow the serialize parameter to not be passed, in this case use the ``deserialize`` parameter by name. .. code-block:: python lowername = fields.Function(deserialize=lambda name: name.lower()) # or lowername = fields.Method(deserialize="lowername") Upgrading to 2.0 ++++++++++++++++ Deserializing `None` ******************** In 2.0, validation/deserialization of `None` is consistent across field types. If ``allow_none`` is `False` (the default), validation fails when the field's value is `None`. If ``allow_none`` is `True`, `None` is considered valid, and the field deserializes to `None`. .. code-block:: python from marshmallow import fields # In 1.0, deserialization of None was inconsistent fields.Int().deserialize(None) # 0 fields.Str().deserialize(None) # '' fields.DateTime().deserialize(None) # error: Could not deserialize None to a datetime. # In 2.0, validation/deserialization of None is consistent fields.Int().deserialize(None) # error: Field may not be null. fields.Str().deserialize(None) # error: Field may not be null. fields.DateTime().deserialize(None) # error: Field may not be null. # allow_none makes None a valid value fields.Int(allow_none=True).deserialize(None) # None Default values ************** Before version 2.0, certain fields (including `String `, `List `, `Nested `, and number fields) had implicit default values that would be used if their corresponding input value was `None` or missing. In 2.0, these implicit defaults are removed. A `Field's ` ``default`` parameter is only used if you explicitly set it. Otherwise, missing inputs will be excluded from the serialized output. .. code-block:: python from marshmallow import Schema, fields class MySchema(Schema): str_no_default = fields.Str() int_no_default = fields.Int() list_no_default = fields.List(fields.Str) schema = MySchema() # In 1.0, None was treated as a missing input, so implicit default values were used schema.dump( {"str_no_default": None, "int_no_default": None, "list_no_default": None} ).data # {'str_no_default': '', 'int_no_default': 0, 'list_no_default': []} # In 2.0, None serializes to None. No more implicit defaults. schema.dump( {"str_no_default": None, "int_no_default": None, "list_no_default": None} ).data # {'str_no_default': None, 'int_no_default': None, 'list_no_default': None} .. code-block:: python # In 1.0, implicit default values were used for missing inputs schema.dump({}).data # {'int_no_default': 0, 'str_no_default': '', 'list_no_default': []} # In 2.0, missing inputs are excluded from the serialized output # if no defaults are specified schema.dump({}).data # {} As a consequence of this new behavior, the ``skip_missing`` `class Meta ` option has been removed. Pre-processing and post-processing methods ****************************************** The pre- and post-processing API was significantly improved for better consistency and flexibility. The `pre_load `, `post_load `, `pre_dump `, and `post_dump ` should be used to define processing hooks. ``Schema.preprocessor`` and ``Schema.data_handler`` are removed. .. code-block:: python # 1.0 API from marshmallow import Schema, fields class ExampleSchema(Schema): field_a = fields.Int() @ExampleSchema.preprocessor def increment(schema, data): data["field_a"] += 1 return data @ExampleSchema.data_handler def decrement(schema, data, obj): data["field_a"] -= 1 return data # 2.0 API from marshmallow import Schema, fields, pre_load, post_dump class ExampleSchema(Schema): field_a = fields.Int() @pre_load def increment(self, data): data["field_a"] += 1 return data @post_dump def decrement(self, data): data["field_a"] -= 1 return data See the :doc:`extending/pre_and_post_processing_methods` page for more information on the ``pre_*`` and ``post_*`` decorators. Schema validators ***************** Similar to pre-processing and post-processing methods, schema validators are now defined as methods. Decorate schema validators with `validates_schema `. ``Schema.validator`` is removed. .. code-block:: python # 1.0 API from marshmallow import Schema, fields, ValidationError class MySchema(Schema): field_a = fields.Int(required=True) field_b = fields.Int(required=True) @ExampleSchema.validator def validate_schema(schema, data): if data["field_a"] < data["field_b"]: raise ValidationError("field_a must be greater than field_b") # 2.0 API from marshmallow import Schema, fields, validates_schema, ValidationError class MySchema(Schema): field_a = fields.Int(required=True) field_b = fields.Int(required=True) @validates_schema def validate_schema(self, data): if data["field_a"] < data["field_b"]: raise ValidationError("field_a must be greater than field_b") Custom accessors and error handlers *********************************** Custom accessors and error handlers are now defined as methods. ``Schema.accessor`` and ``Schema.error_handler`` are deprecated. .. code-block:: python from marshmallow import Schema, fields # 1.0 Deprecated API class ExampleSchema(Schema): field_a = fields.Int() @ExampleSchema.accessor def get_from_dict(schema, attr, obj, default=None): return obj.get(attr, default) @ExampleSchema.error_handler def handle_errors(schema, errors, obj): raise CustomError("Something bad happened", messages=errors) # 2.0 API class ExampleSchema(Schema): field_a = fields.Int() def get_attribute(self, attr, obj, default): return obj.get(attr, default) # handle_error gets passed a ValidationError def handle_error(self, exc, data): raise CustomError("Something bad happened", messages=exc.messages) Use `post_load ` instead of `make_object` *************************************************************************** The `make_object` method was deprecated from the `Schema ` API (see :issue:`277` for the rationale). In order to deserialize to an object, use a `post_load ` method. .. code-block:: python # 1.0 from marshmallow import Schema, fields, post_load class UserSchema(Schema): name = fields.Str() created_at = fields.DateTime() def make_object(self, data): return User(**data) # 2.0 from marshmallow import Schema, fields, post_load class UserSchema(Schema): name = fields.Str() created_at = fields.DateTime() @post_load def make_user(self, data): return User(**data) Error format when ``many=True`` ******************************* When validating a collection (i.e. when calling ``load`` or ``dump`` with ``many=True``), the errors dictionary will be keyed on the indices of invalid items. .. code-block:: python from marshmallow import Schema, fields class BandMemberSchema(Schema): name = fields.String(required=True) email = fields.Email() user_data = [ {"email": "mick@stones.com", "name": "Mick"}, {"email": "invalid", "name": "Invalid"}, # invalid email {"email": "keith@stones.com", "name": "Keith"}, {"email": "charlie@stones.com"}, # missing "name" ] result = BandMemberSchema(many=True).load(user_data) # 1.0 result.errors # {'email': ['"invalid" is not a valid email address.'], # 'name': ['Missing data for required field.']} # 2.0 result.errors # {1: {'email': ['"invalid" is not a valid email address.']}, # 3: {'name': ['Missing data for required field.']}} You can still get the pre-2.0 behavior by setting ``index_errors = False`` in a ``Schema's`` `class Meta ` options. Use ``ValidationError`` instead of ``MarshallingError`` and ``UnmarshallingError`` ********************************************************************************** The :exc:`MarshallingError` and :exc:`UnmarshallingError` exceptions are deprecated in favor of a single :exc:`ValidationError `. Users who have written custom fields or are using ``strict`` mode will need to change their code accordingly. Handle ``ValidationError`` in strict mode ----------------------------------------- When using `strict` mode, you should handle `ValidationErrors` when calling `Schema.dump ` and `Schema.load `. .. code-block:: python from marshmallow import exceptions as exc schema = BandMemberSchema(strict=True) # 1.0 try: schema.load({"email": "invalid-email"}) except exc.UnmarshallingError as err: handle_error(err) # 2.0 try: schema.load({"email": "invalid-email"}) except exc.ValidationError as err: handle_error(err) Accessing error messages in strict mode *************************************** In 2.0, `strict` mode was improved so that you can access all error messages for a schema (rather than failing early) by accessing a `ValidationError's` ``messages`` attribute. .. code-block:: python schema = BandMemberSchema(strict=True) try: result = schema.load({"email": "invalid"}) except ValidationMessage as err: print(err.messages) # { # 'email': ['"invalid" is not a valid email address.'], # 'name': ['Missing data for required field.'] # } Custom fields ************* Two changes must be made to make your custom fields compatible with version 2.0. - The `_deserialize ` method of custom fields now receives ``attr`` (the key corresponding to the value to be deserialized) and the raw input ``data`` as arguments. - Custom fields should raise :exc:`ValidationError ` in their `_deserialize` and `_serialize` methods when a validation error occurs. .. code-block:: python from marshmallow import fields, ValidationError from marshmallow.exceptions import UnmarshallingError # In 1.0, an UnmarshallingError was raised class PasswordField(fields.Field): def _deserialize(self, val): if not len(val) >= 6: raise UnmarshallingError("Password too short.") return val # In 2.0, _deserialize receives attr and data, # and a ValidationError is raised class PasswordField(fields.Field): def _deserialize(self, val, attr, data): if not len(val) >= 6: raise ValidationError("Password too short.") return val To make a field compatible with both marshmallow 1.x and 2.x, you can pass `*args` and `**kwargs` to the signature. .. code-block:: python class PasswordField(fields.Field): def _deserialize(self, val, *args, **kwargs): if not len(val) >= 6: raise ValidationError("Password too short.") return val Custom error messages ********************* Error messages can be customized at the `Field` class or instance level. .. code-block:: python # 1.0 field = fields.Number(error="You passed a bad number") # 2.0 # Instance-level field = fields.Number(error_messages={"invalid": "You passed a bad number."}) # Class-level class MyNumberField(fields.Number): default_error_messages = {"invalid": "You passed a bad number."} Passing a string to ``required`` is deprecated. .. code-block:: python # 1.0 field = fields.Str(required="Missing required argument.") # 2.0 field = fields.Str(error_messages={"required": "Missing required argument."}) Use ``OneOf`` instead of ``fields.Select`` ****************************************** The `fields.Select` field is deprecated in favor of the newly-added `OneOf` validator. .. code-block:: python from marshmallow import fields from marshmallow.validate import OneOf # 1.0 fields.Select(["red", "blue"]) # 2.0 fields.Str(validate=OneOf(["red", "blue"])) Accessing context from method fields ************************************ Use ``self.context`` to access a schema's context within a ``Method`` field. .. code-block:: python class UserSchema(Schema): name = fields.String() likes_bikes = fields.Method("writes_about_bikes") def writes_about_bikes(self, user): return "bicycle" in self.context["blog"].title.lower() Validation error messages ************************* The default error messages for many fields and validators have been changed for better consistency. .. code-block:: python from marshmallow import Schema, fields, validate class ValidatingSchema(Schema): foo = fields.Str() bar = fields.Bool() baz = fields.Int() qux = fields.Float() spam = fields.Decimal(2, 2) eggs = fields.DateTime() email = fields.Str(validate=validate.Email()) homepage = fields.Str(validate=validate.URL()) nums = fields.List(fields.Int()) schema = ValidatingSchema() invalid_data = { "foo": 42, "bar": 24, "baz": "invalid-integer", "qux": "invalid-float", "spam": "invalid-decimal", "eggs": "invalid-datetime", "email": "invalid-email", "homepage": "invalid-url", "nums": "invalid-list", } errors = schema.validate(invalid_data) # { # 'foo': ['Not a valid string.'], # 'bar': ['Not a valid boolean.'], # 'baz': ['Not a valid integer.'], # 'qux': ['Not a valid number.'], # 'spam': ['Not a valid number.'] # 'eggs': ['Not a valid datetime.'], # 'email': ['Not a valid email address.'], # 'homepage': ['Not a valid URL.'], # 'nums': ['Not a valid list.'], # } More **** For a full list of changes in 2.0, see the :doc:`changelog `. Upgrading to 1.2 ++++++++++++++++ Validators ********** Validators were rewritten as class-based callables, making them easier to use when declaring fields. .. code-block:: python from marshmallow import fields # 1.2 from marshmallow.validate import Range age = fields.Int(validate=[Range(min=0, max=999)]) # Pre-1.2 from marshmallow.validate import ranging age = fields.Int(validate=[lambda val: ranging(val, min=0, max=999)]) The validator functions from 1.1 are deprecated and will be removed in 2.0. Deserializing the empty string ****************************** In version 1.2, deserialization of the empty string (``''``) with `DateTime`, `Date`, `Time`, or `TimeDelta` fields results in consistent error messages, regardless of whether or not `python-dateutil` is installed. .. code-block:: python from marshmallow import fields fields.Date().deserialize("") # UnmarshallingError: Could not deserialize '' to a date object. Decimal ******* The `Decimal` field was added to support serialization/deserialization of `decimal.Decimal` numbers. You should use this field when dealing with numbers where precision is critical. The `Fixed`, `Price`, and `Arbitrary` fields are deprecated in favor the `Decimal` field. Upgrading to 1.0 ++++++++++++++++ Version 1.0 marks the first major release of marshmallow. Many big changes were made from the pre-1.0 releases in order to provide a cleaner API, support object deserialization, and improve field validation. Perhaps the largest change is in how objects get serialized. Serialization occurs by invoking the :meth:`Schema.dump` method rather than passing the object to the constructor. Because only configuration options (e.g. the ``many``, ``strict``, and ``only`` parameters) are passed to the constructor, you can more easily reuse serializer instances. The :meth:`dump ` method also forms a nice symmetry with the :meth:`Schema.load` method, which is used for deserialization. .. code-block:: python from marshmallow import Schema, fields class UserSchema(Schema): email = fields.Email() name = fields.String() user = User(email="monty@python.org", name="Monty Python") # 1.0 serializer = UserSchema() data, errors = serializer.dump(user) # OR result = serializer.dump(user) result.data # => serialized result result.errors # => errors # Pre-1.0 serialized = UserSchema(user) data = serialized.data errors = serialized.errors .. note:: Some crucial parts of the pre-1.0 API have been retained to ease the transition. You can still pass an object to a `Schema ` constructor and access the `Schema.data` and `Schema.errors` properties. The `is_valid` method, however, has been completely removed. It is recommended that you migrate to the new API to prevent future releases from breaking your code. The Fields interface was also reworked in 1.0 to make it easier to define custom fields with their own serialization and deserialization behavior. Custom fields now implement :meth:`Field._serialize` and :meth:`Field._deserialize`. .. code-block:: python from marshmallow import fields, MarshallingError class PasswordField(fields.Field): def _serialize(self, value, attr, obj): if not value or len(value) < 6: raise MarshallingError("Password must be greater than 6 characters.") return str(value).strip() # Similarly, you can override the _deserialize method Another major change in 1.0 is that multiple validation errors can be stored for a single field. The ``errors`` dictionary returned by :meth:`Schema.dump` and :meth:`Schema.load` is a list of error messages keyed by field name. .. code-block:: python from marshmallow import Schema, fields, ValidationError def must_have_number(val): if not any(ch.isdigit() for ch in val): raise ValidationError("Value must have an number.") def validate_length(val): if len(val) < 8: raise ValidationError("Value must have 8 or more characters.") class ValidatingSchema(Schema): password = fields.String(validate=[must_have_number, validate_length]) result, errors = ValidatingSchema().load({"password": "secure"}) print(errors) # {'password': ['Value must have an number.', # 'Value must have 8 or more characters.']} Other notable changes: - Serialized output is no longer an ``OrderedDict`` by default. You must explicitly set the `ordered` `class Meta ` option to `True` . - ``Serializer`` has been renamed to `Schema `, but you can still import ``marshmallow.Serializer`` (which is aliased to `Schema `). - ``datetime`` objects serialize to ISO8601-formatted strings by default (instead of RFC821 format). - The ``fields.validated`` decorator was removed, as it is no longer necessary given the new Fields interface. - ``Schema.factory`` class method was removed. .. seealso:: See the :doc:`changelog ` for a more complete listing of added features, bugfixes and breaking changes. marshmallow-3.26.1/docs/whos_using.rst000066400000000000000000000004171475016050200200020ustar00rootroot00000000000000Who's using marshmallow? ======================== Visit the link below to see a list of companies using marshmallow. https://github.com/marshmallow-code/marshmallow/wiki/Who's-using-marshmallow%3F Is your company or organization using marshmallow? Add it to the wiki. marshmallow-3.26.1/docs/why.rst000066400000000000000000000124541475016050200164300ustar00rootroot00000000000000Why marshmallow? ================ The Python ecosystem has many great libraries for data formatting and schema validation. In fact, marshmallow was influenced by a number of these libraries. marshmallow is inspired by `Django REST Framework`_, `Flask-RESTful`_, and `colander `_. It borrows a number of implementation and design ideas from these libraries to create a flexible and productive solution for marshalling, unmarshalling, and validating data. Here are just a few reasons why you might use marshmallow. Agnostic -------- marshmallow makes no assumption about web frameworks or database layers. It will work with just about any ORM, ODM, or no ORM at all. This gives you the freedom to choose the components that fit your application's needs without having to change your data formatting code. If you wish, you can build integration layers to make marshmallow work more closely with your frameworks and libraries of choice (for examples, see `Flask-Marshmallow `_ and `Django REST Marshmallow `_). Concise, familiar syntax ------------------------ If you have used `Django REST Framework`_ or `WTForms `_, marshmallow's :class:`Schema ` syntax will feel familiar to you. Class-level field attributes define the schema for formatting your data. Configuration is added using the `class Meta ` paradigm. Configuration options can be overridden at application runtime by passing arguments to the `Schema ` constructor. The :meth:`dump ` and :meth:`load ` methods are used for serialization and deserialization (of course!). Class-based schemas allow for code reuse and configuration ---------------------------------------------------------- Unlike `Flask-RESTful`_, which uses dictionaries to define output schemas, marshmallow uses classes. This allows for easy code reuse and configuration. It also enables patterns for configuring and extending schemas, such as adding :doc:`post-processing and error handling behavior `. Consistency meets flexibility ----------------------------- marshmallow makes it easy to modify a schema's output at application runtime. A single :class:`Schema ` can produce multiple output formats while keeping the individual field outputs consistent. As an example, you might have a JSON endpoint for retrieving all information about a video game's state. You then add a low-latency endpoint that only returns a minimal subset of information about game state. Both endpoints can be handled by the same `Schema `. .. code-block:: python class GameStateSchema(Schema): _id = fields.UUID(required=True) score = fields.Nested(ScoreSchema) players = fields.List(fields.Nested(PlayerSchema)) last_changed = fields.DateTime(format="rfc") # Serializes full game state full_serializer = GameStateSchema() # Serializes a subset of information, for a low-latency endpoint summary_serializer = GameStateSchema(only=("_id", "last_changed")) # Also filter the fields when serializing multiple games gamelist_serializer = GameStateSchema( many=True, only=("_id", "players", "last_changed") ) In this example, a single schema produced three different outputs! The dynamic nature of a :class:`Schema` leads to **less code** and **more consistent formatting**. .. _Django REST Framework: https://www.django-rest-framework.org/ .. _Flask-RESTful: https://flask-restful.readthedocs.io/ Context-aware serialization --------------------------- marshmallow schemas can modify their output based on the context in which they are used. Field objects have access to a ``context`` dictionary that can be changed at runtime. Here's a simple example that shows how a `Schema ` can anonymize a person's name when a boolean is set on the context. .. code-block:: python class PersonSchema(Schema): id = fields.Integer() name = fields.Method("get_name") def get_name(self, person, context): if context.get("anonymize"): return "" return person.name person = Person(name="Monty") schema = PersonSchema() schema.dump(person) # {'id': 143, 'name': 'Monty'} # In a different context, anonymize the name schema.context["anonymize"] = True schema.dump(person) # {'id': 143, 'name': ''} .. seealso:: See the relevant section of the :ref:`usage guide ` to learn more about context-aware serialization. Advanced schema nesting ----------------------- Most serialization libraries provide some means for nesting schemas within each other, but they often fail to meet common use cases in clean way. marshmallow aims to fill these gaps by adding a few nice features for :doc:`nesting schemas `: - You can specify which :ref:`subset of fields ` to include on nested schemas. - :ref:`Two-way nesting `. Two different schemas can nest each other. - :ref:`Self-nesting `. A schema can be nested within itself. marshmallow-3.26.1/examples/000077500000000000000000000000001475016050200157475ustar00rootroot00000000000000marshmallow-3.26.1/examples/flask_example.py000066400000000000000000000107061475016050200211400ustar00rootroot00000000000000# /// script # requires-python = ">=3.9" # dependencies = [ # "flask", # "flask-sqlalchemy>=3.1.1", # "marshmallow", # "sqlalchemy>2.0", # ] # /// from __future__ import annotations import datetime from flask import Flask, request from flask_sqlalchemy import SQLAlchemy from sqlalchemy.exc import NoResultFound from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship from marshmallow import Schema, ValidationError, fields, pre_load app = Flask(__name__) app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:////tmp/quotes.db" class Base(DeclarativeBase): pass db = SQLAlchemy(app, model_class=Base) ##### MODELS ##### class Author(db.Model): # type: ignore[name-defined] id: Mapped[int] = mapped_column(primary_key=True) first: Mapped[str] last: Mapped[str] class Quote(db.Model): # type: ignore[name-defined] id: Mapped[int] = mapped_column(primary_key=True) content: Mapped[str] = mapped_column(nullable=False) author_id: Mapped[int] = mapped_column(db.ForeignKey(Author.id)) author: Mapped[Author] = relationship(backref=db.backref("quotes", lazy="dynamic")) posted_at: Mapped[datetime.datetime] ##### SCHEMAS ##### class AuthorSchema(Schema): id = fields.Int(dump_only=True) first = fields.Str() last = fields.Str() formatted_name = fields.Method("format_name", dump_only=True) def format_name(self, author): return f"{author.last}, {author.first}" # Custom validator def must_not_be_blank(data): if not data: raise ValidationError("Data not provided.") class QuoteSchema(Schema): id = fields.Int(dump_only=True) author = fields.Nested(AuthorSchema, validate=must_not_be_blank) content = fields.Str(required=True, validate=must_not_be_blank) posted_at = fields.DateTime(dump_only=True) # Allow client to pass author's full name in request body # e.g. {"author': 'Tim Peters"} rather than {"first": "Tim", "last": "Peters"} @pre_load def process_author(self, data, **kwargs): author_name = data.get("author") if author_name: first, last = author_name.split(" ") author_dict = {"first": first, "last": last} else: author_dict = {} data["author"] = author_dict return data author_schema = AuthorSchema() authors_schema = AuthorSchema(many=True) quote_schema = QuoteSchema() quotes_schema = QuoteSchema(many=True, only=("id", "content")) ##### API ##### @app.route("/authors") def get_authors(): authors = Author.query.all() # Serialize the queryset result = authors_schema.dump(authors) return {"authors": result} @app.route("/authors/") def get_author(pk): try: author = Author.query.filter(Author.id == pk).one() except NoResultFound: return {"message": "Author could not be found."}, 400 author_result = author_schema.dump(author) quotes_result = quotes_schema.dump(author.quotes.all()) return {"author": author_result, "quotes": quotes_result} @app.route("/quotes/", methods=["GET"]) def get_quotes(): quotes = Quote.query.all() result = quotes_schema.dump(quotes, many=True) return {"quotes": result} @app.route("/quotes/") def get_quote(pk): try: quote = Quote.query.filter(Quote.id == pk).one() except NoResultFound: return {"message": "Quote could not be found."}, 400 result = quote_schema.dump(quote) return {"quote": result} @app.route("/quotes/", methods=["POST"]) def new_quote(): json_data = request.get_json() if not json_data: return {"message": "No input data provided"}, 400 # Validate and deserialize input try: data = quote_schema.load(json_data) except ValidationError as err: return err.messages, 422 first, last = data["author"]["first"], data["author"]["last"] author = Author.query.filter_by(first=first, last=last).first() if author is None: # Create a new author author = Author(first=first, last=last) db.session.add(author) # Create new quote quote = Quote( content=data["content"], author=author, posted_at=datetime.datetime.now(datetime.UTC), ) db.session.add(quote) db.session.commit() result = quote_schema.dump(Quote.query.get(quote.id)) return {"message": "Created new quote.", "quote": result} if __name__ == "__main__": with app.app_context(): db.create_all() app.run(debug=True, port=5000) marshmallow-3.26.1/examples/inflection_example.py000066400000000000000000000016311475016050200221670ustar00rootroot00000000000000# /// script # requires-python = ">=3.9" # dependencies = [ # "marshmallow", # ] # /// from marshmallow import Schema, fields def camelcase(s): parts = iter(s.split("_")) return next(parts) + "".join(i.title() for i in parts) class CamelCaseSchema(Schema): """Schema that uses camel-case for its external representation and snake-case for its internal representation. """ def on_bind_field(self, field_name, field_obj): field_obj.data_key = camelcase(field_obj.data_key or field_name) # ----------------------------------------------------------------------------- class UserSchema(CamelCaseSchema): first_name = fields.Str(required=True) last_name = fields.Str(required=True) schema = UserSchema() loaded = schema.load({"firstName": "David", "lastName": "Bowie"}) print("Loaded data:") print(loaded) dumped = schema.dump(loaded) print("Dumped data:") print(dumped) marshmallow-3.26.1/examples/invalid_package.json000066400000000000000000000002251475016050200217420ustar00rootroot00000000000000{ "name": "dunderscore", "version": "INVALID", "homepage": "INVALID", "description": "The Pythonic JavaScript toolkit", "license": "MIT" } marshmallow-3.26.1/examples/package.json000066400000000000000000000003511475016050200202340ustar00rootroot00000000000000{ "name": "dunderscore", "version": "1.2.3", "description": "The Pythonic JavaScript toolkit", "devDependencies": { "pest": "^23.4.1" }, "main": "index.js", "scripts": { "test": "pest" }, "license": "MIT" } marshmallow-3.26.1/examples/package_json_example.py000066400000000000000000000030541475016050200224620ustar00rootroot00000000000000# /// script # requires-python = ">=3.9" # dependencies = [ # "marshmallow", # "packaging>=17.0", # ] # /// import json import sys from pprint import pprint from packaging import version from marshmallow import INCLUDE, Schema, ValidationError, fields class Version(fields.Field): """Version field that deserializes to a Version object.""" def _deserialize(self, value, *args, **kwargs): try: return version.Version(value) except version.InvalidVersion as e: raise ValidationError("Not a valid version.") from e def _serialize(self, value, *args, **kwargs): return str(value) class PackageSchema(Schema): name = fields.Str(required=True) version = Version(required=True) description = fields.Str(required=True) main = fields.Str(required=False) homepage = fields.URL(required=False) scripts = fields.Dict(keys=fields.Str(), values=fields.Str()) license = fields.Str(required=True) dependencies = fields.Dict(keys=fields.Str(), values=fields.Str(), required=False) dev_dependencies = fields.Dict( keys=fields.Str(), values=fields.Str(), required=False, data_key="devDependencies", ) class Meta: # Include unknown fields in the deserialized output unknown = INCLUDE if __name__ == "__main__": pkg = json.load(sys.stdin) try: pprint(PackageSchema().load(pkg)) except ValidationError as error: print("ERROR: package.json is invalid") pprint(error.messages) sys.exit(1) marshmallow-3.26.1/performance/000077500000000000000000000000001475016050200164325ustar00rootroot00000000000000marshmallow-3.26.1/performance/benchmark.py000066400000000000000000000074231475016050200207440ustar00rootroot00000000000000"""Simple benchmark for marshmallow serialization of a moderately complex object. Uses the `timeit` module to benchmark serializing an object through marshmallow. """ # ruff: noqa: A002, T201 import argparse import cProfile import datetime import gc import timeit from marshmallow import Schema, ValidationError, fields, post_dump # Custom validator def must_not_be_blank(data): if not data: raise ValidationError("Data not provided.") class AuthorSchema(Schema): id = fields.Int(dump_only=True) first = fields.Str() last = fields.Str() book_count = fields.Float() age = fields.Float() address = fields.Str() full_name = fields.Method("get_full_name") def get_full_name(self, author): return f"{author.last}, {author.first}" class QuoteSchema(Schema): id = fields.Int(dump_only=True) author = fields.Nested(AuthorSchema, validate=must_not_be_blank) content = fields.Str(required=True, validate=must_not_be_blank) posted_at = fields.DateTime(dump_only=True) book_name = fields.Str() page_number = fields.Float() line_number = fields.Float() col_number = fields.Float() @post_dump def add_full_name(self, data, **kwargs): data["author_full"] = "{}, {}".format( data["author"]["last"], data["author"]["first"] ) return data class Author: def __init__(self, id, first, last, book_count, age, address): self.id = id self.first = first self.last = last self.book_count = book_count self.age = age self.address = address class Quote: def __init__( self, id, author, content, posted_at, book_name, page_number, line_number, col_number, ): self.id = id self.author = author self.content = content self.posted_at = posted_at self.book_name = book_name self.page_number = page_number self.line_number = line_number self.col_number = col_number def run_timeit(quotes, iterations, repeat, *, profile=False): quotes_schema = QuoteSchema(many=True) if profile: profile = cProfile.Profile() profile.enable() gc.collect() best = min( timeit.repeat( lambda: quotes_schema.dump(quotes), "gc.enable()", number=iterations, repeat=repeat, ) ) if profile: profile.disable() profile.dump_stats("marshmallow.pprof") return best * 1e6 / iterations / len(quotes) def main(): parser = argparse.ArgumentParser(description="Runs a benchmark of Marshmallow.") parser.add_argument( "--iterations", type=int, default=1000, help="Number of iterations to run per test.", ) parser.add_argument( "--repeat", type=int, default=5, help="Number of times to repeat the performance test. The minimum will " "be used.", ) parser.add_argument( "--object-count", type=int, default=20, help="Number of objects to dump." ) parser.add_argument( "--profile", action="store_true", help="Whether or not to profile marshmallow while running the benchmark.", ) args = parser.parse_args() quotes = [ Quote( i, Author(i, "Foo", "Bar", 42, 66, "123 Fake St"), "Hello World", datetime.datetime(2019, 7, 4, tzinfo=datetime.timezone.utc), "The World", 34, 3, 70, ) for i in range(args.object_count) ] print( f"Benchmark Result: {run_timeit(quotes, args.iterations, args.repeat, profile=args.profile):.2f} usec/dump" ) if __name__ == "__main__": main() marshmallow-3.26.1/pyproject.toml000066400000000000000000000111761475016050200170530ustar00rootroot00000000000000[project] name = "marshmallow" version = "3.26.1" description = "A lightweight library for converting complex datatypes to and from native Python datatypes." readme = "README.rst" license = { file = "LICENSE" } authors = [{ name = "Steven Loria", email = "sloria1@gmail.com" }] maintainers = [ { name = "Steven Loria", email = "sloria1@gmail.com" }, { name = "Jérôme Lafréchoux", email = "jerome@jolimont.fr" }, { name = "Jared Deckard", email = "jared@shademaps.com" }, ] classifiers = [ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", ] requires-python = ">=3.9" dependencies = ["packaging>=17.0"] [project.urls] Changelog = "https://marshmallow.readthedocs.io/en/latest/changelog.html" Funding = "https://opencollective.com/marshmallow" Issues = "https://github.com/marshmallow-code/marshmallow/issues" Source = "https://github.com/marshmallow-code/marshmallow" Tidelift = "https://tidelift.com/subscription/pkg/pypi-marshmallow?utm_source=pypi-marshmallow&utm_medium=pypi" [project.optional-dependencies] docs = [ "autodocsumm==0.2.14", "furo==2024.8.6", "sphinx-copybutton==0.5.2", "sphinx-issues==5.0.0", "sphinx==8.1.3", "sphinxext-opengraph==0.9.1", ] tests = ["pytest", "simplejson"] dev = ["marshmallow[tests]", "tox", "pre-commit>=3.5,<5.0"] [build-system] requires = ["flit_core<4"] build-backend = "flit_core.buildapi" [tool.flit.sdist] include = [ "docs/", "tests/", "CHANGELOG.rst", "CONTRIBUTING.rst", "SECURITY.md", "NOTICE", "tox.ini", ] exclude = ["docs/_build/"] [tool.ruff] fix = true [tool.ruff.format] docstring-code-format = true [tool.ruff.lint] # use all checks available in ruff except the ones explicitly ignored below select = ["ALL"] ignore = [ "A005", # "module {name} shadows a Python standard-library module" "ANN", # let mypy handle annotation checks "ARG", # unused arguments are common w/ interfaces "COM", # let formatter take care commas "C901", # don't enforce complexity level "D", # don't require docstrings "DTZ007", # ignore false positives due to https://github.com/astral-sh/ruff/issues/1306 "E501", # leave line-length enforcement to formatter "EM", # allow string messages in exceptions "FIX", # allow "FIX" comments in code "INP001", # allow Python files outside of packages "N806", # allow uppercase variable names for variables that are classes "PERF203", # allow try-except within loops "PLR0913", # "Too many arguments" "PLR0912", # "Too many branches" "PLR2004", # "Magic value used in comparison" "PTH", # don't require using pathlib instead of os "RUF012", # allow mutable class variables "SIM102", # Sometimes nested ifs are more readable than if...and... "SIM105", # "Use `contextlib.suppress(...)` instead of `try`-`except`-`pass`" "SIM108", # sometimes if-else is more readable than a ternary "TD", # allow TODO comments to be whatever we want "TRY003", # allow long messages passed to exceptions "TRY004", # allow ValueError for invalid argument types ] [tool.ruff.lint.per-file-ignores] "tests/*" = [ "ARG", # unused arguments are fine in tests "C408", # allow dict() instead of dict literal "DTZ", # allow naive datetimes "FBT003", # allow boolean positional argument "N803", # fixture names might be uppercase "PLR0915", # allow lots of statements "PT007", # ignore false positives due to https://github.com/astral-sh/ruff/issues/14743 "PT011", # don't require match when using pytest.raises "S", # allow asserts "SIM117", # allow nested with statements because it's more readable sometimes "SLF001", # allow private attribute access ] "examples/*" = [ "S", # allow asserts "T", # allow prints ] "src/marshmallow/orderedset.py" = [ "FBT002", # allow boolean positional argument "T", # allow prints ] [tool.mypy] files = ["src", "tests", "examples"] ignore_missing_imports = true warn_unreachable = true warn_unused_ignores = true warn_redundant_casts = true no_implicit_optional = true [tool.pytest.ini_options] norecursedirs = ".git .ropeproject .tox docs env venv tests/mypy_test_cases" filterwarnings = [ "ignore:Returning `False` from a validator:marshmallow.warnings.ChangedInMarshmallow4Warning", "ignore:The `ordered` `class Meta` option is deprecated.:marshmallow.warnings.RemovedInMarshmallow4Warning", ] marshmallow-3.26.1/src/000077500000000000000000000000001475016050200147205ustar00rootroot00000000000000marshmallow-3.26.1/src/marshmallow/000077500000000000000000000000001475016050200172465ustar00rootroot00000000000000marshmallow-3.26.1/src/marshmallow/__init__.py000066400000000000000000000045231475016050200213630ustar00rootroot00000000000000from __future__ import annotations import importlib.metadata import typing from packaging.version import Version from marshmallow.decorators import ( post_dump, post_load, 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 . import fields def __getattr__(name: str) -> typing.Any: import warnings if name == "__version__": warnings.warn( "The '__version__' attribute is deprecated and will be removed in" " in a future version. Use feature detection or" " 'importlib.metadata.version(\"marshmallow\")' instead.", DeprecationWarning, stacklevel=2, ) return importlib.metadata.version("marshmallow") if name == "__parsed_version__": warnings.warn( "The '__parsed_version__' attribute is deprecated and will be removed in" " in a future version. Use feature detection or" " 'packaging.Version(importlib.metadata.version(\"marshmallow\"))' instead.", DeprecationWarning, stacklevel=2, ) return Version(importlib.metadata.version("marshmallow")) if name == "__version_info__": warnings.warn( "The '__version_info__' attribute is deprecated and will be removed in" " in a future version. Use feature detection or" " 'packaging.Version(importlib.metadata.version(\"marshmallow\")).release' instead.", DeprecationWarning, stacklevel=2, ) __parsed_version__ = Version(importlib.metadata.version("marshmallow")) __version_info__: tuple[int, int, int] | tuple[int, int, int, str, int] = ( __parsed_version__.release # type: ignore[assignment] ) if __parsed_version__.pre: __version_info__ += __parsed_version__.pre # type: ignore[assignment] return __version_info__ raise AttributeError(name) __all__ = [ "EXCLUDE", "INCLUDE", "RAISE", "Schema", "SchemaOpts", "ValidationError", "fields", "missing", "post_dump", "post_load", "pprint", "pre_dump", "pre_load", "validates", "validates_schema", ] marshmallow-3.26.1/src/marshmallow/base.py000066400000000000000000000025221475016050200205330ustar00rootroot00000000000000"""Abstract base classes. These are necessary to avoid circular imports between schema.py and fields.py. .. warning:: This module is deprecated. Users should not import from this module. Use `marshmallow.fields.Field` and `marshmallow.schema.Schema` as base classes instead. """ from __future__ import annotations from abc import ABC, abstractmethod class FieldABC(ABC): """Abstract base class from which all Field classes inherit.""" @abstractmethod def serialize(self, attr, obj, accessor=None): pass @abstractmethod def deserialize(self, value): pass @abstractmethod def _serialize(self, value, attr, obj, **kwargs): pass @abstractmethod def _deserialize(self, value, attr, data, **kwargs): pass class SchemaABC(ABC): """Abstract base class from which all Schemas inherit.""" @abstractmethod def dump(self, obj, *, many: bool | None = None): pass @abstractmethod def dumps(self, obj, *, many: bool | None = None): pass @abstractmethod def load(self, data, *, many: bool | None = None, partial=None, unknown=None): pass @abstractmethod def loads( self, json_data, *, many: bool | None = None, partial=None, unknown=None, **kwargs, ): pass marshmallow-3.26.1/src/marshmallow/class_registry.py000066400000000000000000000057251475016050200226660ustar00rootroot00000000000000"""A registry of :class:`Schema ` classes. This allows for string lookup of schemas, which may be used with class:`fields.Nested `. .. warning:: This module is treated as private API. Users should not need to use this module directly. """ # ruff: noqa: ERA001 from __future__ import annotations import typing from marshmallow.exceptions import RegistryError if typing.TYPE_CHECKING: from marshmallow import Schema SchemaType = type[Schema] # { # : # : # } _registry = {} # type: dict[str, list[SchemaType]] def register(classname: str, cls: SchemaType) -> None: """Add a class to the registry of serializer classes. When a class is registered, an entry for both its classname and its full, module-qualified path are added to the registry. Example: :: class MyClass: pass register("MyClass", MyClass) # Registry: # { # 'MyClass': [path.to.MyClass], # 'path.to.MyClass': [path.to.MyClass], # } """ # Module where the class is located module = cls.__module__ # Full module path to the class # e.g. user.schemas.UserSchema fullpath = f"{module}.{classname}" # If the class is already registered; need to check if the entries are # in the same module as cls to avoid having multiple instances of the same # class in the registry if classname in _registry and not any( each.__module__ == module for each in _registry[classname] ): _registry[classname].append(cls) elif classname not in _registry: _registry[classname] = [cls] # Also register the full path if fullpath not in _registry: _registry.setdefault(fullpath, []).append(cls) else: # If fullpath does exist, replace existing entry _registry[fullpath] = [cls] @typing.overload def get_class(classname: str, *, all: typing.Literal[False] = ...) -> SchemaType: ... @typing.overload def get_class( classname: str, *, all: typing.Literal[True] = ... ) -> list[SchemaType]: ... def get_class(classname: str, *, all: bool = False) -> list[SchemaType] | SchemaType: # noqa: A002 """Retrieve a class from the registry. :raises: `marshmallow.exceptions.RegistryError` if the class cannot be found or if there are multiple entries for the given class name. """ try: classes = _registry[classname] except KeyError as error: raise RegistryError( f"Class with name {classname!r} was not found. You may need " "to import the class." ) from error if len(classes) > 1: if all: return _registry[classname] raise RegistryError( f"Multiple classes with name {classname!r} " "were found. Please use the full, " "module-qualified path." ) return _registry[classname][0] marshmallow-3.26.1/src/marshmallow/decorators.py000066400000000000000000000210551475016050200217700ustar00rootroot00000000000000"""Decorators for registering schema pre-processing and post-processing methods. These should be imported from the top-level `marshmallow` module. Methods decorated with `pre_load `, `post_load `, `pre_dump `, `post_dump `, and `validates_schema ` receive ``many`` as a keyword argument. In addition, `pre_load `, `post_load `, and `validates_schema ` receive ``partial``. If you don't need these arguments, add ``**kwargs`` to your method signature. Example: :: from marshmallow import ( Schema, pre_load, pre_dump, post_load, validates_schema, validates, fields, ValidationError, ) class UserSchema(Schema): email = fields.Str(required=True) age = fields.Integer(required=True) @post_load def lowerstrip_email(self, item, many, **kwargs): item["email"] = item["email"].lower().strip() return item @pre_load(pass_many=True) def remove_envelope(self, data, many, **kwargs): namespace = "results" if many else "result" return data[namespace] @post_dump(pass_many=True) def add_envelope(self, data, many, **kwargs): namespace = "results" if many else "result" return {namespace: data} @validates_schema def validate_email(self, data, **kwargs): if len(data["email"]) < 3: raise ValidationError("Email must be more than 3 characters", "email") @validates("age") def validate_age(self, data, **kwargs): if data < 14: raise ValidationError("Too young!") .. note:: These decorators only work with instance methods. Class and static methods are not supported. .. warning:: The invocation order of decorated methods of the same type is not guaranteed. If you need to guarantee order of different processing steps, you should put them in the same processing method. """ from __future__ import annotations import functools from collections import defaultdict from typing import Any, Callable, cast PRE_DUMP = "pre_dump" POST_DUMP = "post_dump" PRE_LOAD = "pre_load" POST_LOAD = "post_load" VALIDATES = "validates" VALIDATES_SCHEMA = "validates_schema" class MarshmallowHook: __marshmallow_hook__: dict[str, list[tuple[bool, Any]]] | None = None def validates(field_name: str) -> Callable[..., Any]: """Register a field validator. :param field_name: Name of the field that the method validates. """ return set_hook(None, VALIDATES, field_name=field_name) def validates_schema( fn: Callable[..., Any] | None = None, pass_many: bool = False, # noqa: FBT001, FBT002 pass_original: bool = False, # noqa: FBT001, FBT002 skip_on_field_errors: bool = True, # noqa: FBT001, FBT002 ) -> Callable[..., Any]: """Register a schema-level validator. By default it receives a single object at a time, transparently handling the ``many`` argument passed to the `Schema `'s :func:`~marshmallow.Schema.validate` call. If ``pass_many=True``, the raw data (which may be a collection) is passed. If ``pass_original=True``, the original data (before unmarshalling) will be passed as an additional argument to the method. If ``skip_on_field_errors=True``, this validation method will be skipped whenever validation errors have been detected when validating fields. .. versionchanged:: 3.0.0b1 ``skip_on_field_errors`` defaults to `True`. .. versionchanged:: 3.0.0 ``partial`` and ``many`` are always passed as keyword arguments to the decorated method. """ return set_hook( fn, VALIDATES_SCHEMA, many=pass_many, pass_original=pass_original, skip_on_field_errors=skip_on_field_errors, ) def pre_dump( fn: Callable[..., Any] | None = None, pass_many: bool = False, # noqa: FBT001, FBT002 ) -> Callable[..., Any]: """Register a method to invoke before serializing an object. The method receives the object to be serialized and returns the processed object. By default it receives a single object at a time, transparently handling the ``many`` argument passed to the `Schema `'s :func:`~marshmallow.Schema.dump` call. If ``pass_many=True``, the raw data (which may be a collection) is passed. .. versionchanged:: 3.0.0 ``many`` is always passed as a keyword arguments to the decorated method. """ return set_hook(fn, PRE_DUMP, many=pass_many) def post_dump( fn: Callable[..., Any] | None = None, pass_many: bool = False, # noqa: FBT001, FBT002 pass_original: bool = False, # noqa: FBT001, FBT002 ) -> Callable[..., Any]: """Register a method to invoke after serializing an object. The method receives the serialized object and returns the processed object. By default it receives a single object at a time, transparently handling the ``many`` argument passed to the `Schema `'s :func:`~marshmallow.Schema.dump` call. If ``pass_many=True``, the raw data (which may be a collection) is passed. If ``pass_original=True``, the original data (before serializing) will be passed as an additional argument to the method. .. versionchanged:: 3.0.0 ``many`` is always passed as a keyword arguments to the decorated method. """ return set_hook(fn, POST_DUMP, many=pass_many, pass_original=pass_original) def pre_load( fn: Callable[..., Any] | None = None, pass_many: bool = False, # noqa: FBT001, FBT002 ) -> Callable[..., Any]: """Register a method to invoke before deserializing an object. The method receives the data to be deserialized and returns the processed data. By default it receives a single object at a time, transparently handling the ``many`` argument passed to the `Schema `'s :func:`~marshmallow.Schema.load` call. If ``pass_many=True``, the raw data (which may be a collection) is passed. .. versionchanged:: 3.0.0 ``partial`` and ``many`` are always passed as keyword arguments to the decorated method. """ return set_hook(fn, PRE_LOAD, many=pass_many) def post_load( fn: Callable[..., Any] | None = None, pass_many: bool = False, # noqa: FBT001, FBT002 pass_original: bool = False, # noqa: FBT001, FBT002 ) -> Callable[..., Any]: """Register a method to invoke after deserializing an object. The method receives the deserialized data and returns the processed data. By default it receives a single object at a time, transparently handling the ``many`` argument passed to the `Schema `'s :func:`~marshmallow.Schema.load` call. If ``pass_many=True``, the raw data (which may be a collection) is passed. If ``pass_original=True``, the original data (before deserializing) will be passed as an additional argument to the method. .. versionchanged:: 3.0.0 ``partial`` and ``many`` are always passed as keyword arguments to the decorated method. """ return set_hook(fn, POST_LOAD, many=pass_many, pass_original=pass_original) def set_hook( fn: Callable[..., Any] | None, tag: str, many: bool = False, # noqa: FBT001, FBT002 **kwargs: Any, ) -> Callable[..., Any]: """Mark decorated function as a hook to be picked up later. You should not need to use this method directly. .. note:: Currently only works with functions and instance methods. Class and static methods are not supported. :return: Decorated function if supplied, else this decorator with its args bound. """ # Allow using this as either a decorator or a decorator factory. if fn is None: return functools.partial(set_hook, tag=tag, many=many, **kwargs) # Set a __marshmallow_hook__ attribute instead of wrapping in some class, # because I still want this to end up as a normal (unbound) method. function = cast(MarshmallowHook, fn) try: hook_config = function.__marshmallow_hook__ except AttributeError: function.__marshmallow_hook__ = hook_config = defaultdict(list) # Also save the kwargs for the tagged function on # __marshmallow_hook__, keyed by if hook_config is not None: hook_config[tag].append((many, kwargs)) return fn marshmallow-3.26.1/src/marshmallow/error_store.py000066400000000000000000000043001475016050200221620ustar00rootroot00000000000000"""Utilities for storing collections of error messages. .. warning:: This module is treated as private API. Users should not need to use this module directly. """ from marshmallow.exceptions import SCHEMA class ErrorStore: def __init__(self): #: Dictionary of errors stored during serialization self.errors = {} def store_error(self, messages, field_name=SCHEMA, index=None): # field error -> store/merge error messages under field name key # schema error -> if string or list, store/merge under _schema key # -> if dict, store/merge with other top-level keys if field_name != SCHEMA or not isinstance(messages, dict): messages = {field_name: messages} if index is not None: messages = {index: messages} self.errors = merge_errors(self.errors, messages) def merge_errors(errors1, errors2): # noqa: PLR0911 """Deeply merge two error messages. The format of ``errors1`` and ``errors2`` matches the ``message`` parameter of :exc:`marshmallow.exceptions.ValidationError`. """ if not errors1: return errors2 if not errors2: return errors1 if isinstance(errors1, list): if isinstance(errors2, list): return errors1 + errors2 if isinstance(errors2, dict): return dict(errors2, **{SCHEMA: merge_errors(errors1, errors2.get(SCHEMA))}) return [*errors1, errors2] if isinstance(errors1, dict): if isinstance(errors2, list): return dict(errors1, **{SCHEMA: merge_errors(errors1.get(SCHEMA), errors2)}) if isinstance(errors2, dict): errors = dict(errors1) for key, val in errors2.items(): if key in errors: errors[key] = merge_errors(errors[key], val) else: errors[key] = val return errors return dict(errors1, **{SCHEMA: merge_errors(errors1.get(SCHEMA), errors2)}) if isinstance(errors2, list): return [errors1, *errors2] if isinstance(errors2, dict): return dict(errors2, **{SCHEMA: merge_errors(errors1, errors2.get(SCHEMA))}) return [errors1, errors2] marshmallow-3.26.1/src/marshmallow/exceptions.py000066400000000000000000000044261475016050200220070ustar00rootroot00000000000000"""Exception classes for marshmallow-related errors.""" from __future__ import annotations import typing # Key used for schema-level validation errors SCHEMA = "_schema" class MarshmallowError(Exception): """Base class for all marshmallow-related errors.""" class ValidationError(MarshmallowError): """Raised when validation fails on a field or schema. Validators and custom fields should raise this exception. :param message: An error message, list of error messages, or dict of error messages. If a dict, the keys are subitems and the values are error messages. :param field_name: Field name to store the error on. If `None`, the error is stored as schema-level error. :param data: Raw input data. :param valid_data: Valid (de)serialized data. """ def __init__( self, message: str | list | dict, field_name: str = SCHEMA, data: typing.Mapping[str, typing.Any] | typing.Iterable[typing.Mapping[str, typing.Any]] | None = None, valid_data: list[dict[str, typing.Any]] | dict[str, typing.Any] | None = None, **kwargs, ): self.messages = [message] if isinstance(message, (str, bytes)) else message self.field_name = field_name self.data = data self.valid_data = valid_data self.kwargs = kwargs super().__init__(message) def normalized_messages(self): if self.field_name == SCHEMA and isinstance(self.messages, dict): return self.messages return {self.field_name: self.messages} @property def messages_dict(self) -> dict[str, typing.Any]: if not isinstance(self.messages, dict): raise TypeError( "cannot access 'messages_dict' when 'messages' is of type " + type(self.messages).__name__ ) return self.messages class RegistryError(NameError): """Raised when an invalid operation is performed on the serializer class registry. """ class StringNotCollectionError(MarshmallowError, TypeError): """Raised when a string is passed when a list of strings is expected.""" class FieldInstanceResolutionError(MarshmallowError, TypeError): """Raised when schema to instantiate is neither a Schema class nor an instance.""" marshmallow-3.26.1/src/marshmallow/fields.py000066400000000000000000002220041475016050200210660ustar00rootroot00000000000000# ruff: noqa: F841, SLF001 from __future__ import annotations import collections import copy import datetime as dt import decimal import ipaddress import math import numbers import typing import uuid import warnings from collections.abc import Mapping as _Mapping from marshmallow import class_registry, types, utils, validate from marshmallow.base import FieldABC from marshmallow.exceptions import ( FieldInstanceResolutionError, StringNotCollectionError, ValidationError, ) from marshmallow.utils import ( is_aware, is_collection, resolve_field_instance, ) from marshmallow.utils import ( missing as missing_, ) from marshmallow.validate import And, Length from marshmallow.warnings import ( ChangedInMarshmallow4Warning, RemovedInMarshmallow4Warning, ) if typing.TYPE_CHECKING: from enum import Enum as EnumType from marshmallow.schema import Schema, SchemaMeta __all__ = [ "IP", "URL", "UUID", "AwareDateTime", "Bool", "Boolean", "Constant", "Date", "DateTime", "Decimal", "Dict", "Email", "Enum", "Field", "Float", "Function", "IPInterface", "IPv4", "IPv4Interface", "IPv6", "IPv6Interface", "Int", "Integer", "List", "Mapping", "Method", "NaiveDateTime", "Nested", "Number", "Pluck", "Raw", "Str", "String", "Time", "TimeDelta", "Tuple", "Url", ] class Field(FieldABC): """Base field from which other fields inherit. :param dump_default: If set, this value will be used during serialization if the input value is missing. If not set, the field will be excluded from the serialized output if the input value is missing. May be a value or a callable. :param load_default: Default deserialization value for the field if the field is not found in the input data. May be a value or a callable. :param data_key: The name of the dict key in the external representation, i.e. the input of `load` and the output of `dump`. If `None`, the key will match the name of the field. :param attribute: The name of the key/attribute in the internal representation, i.e. the output of `load` and the input of `dump`. If `None`, the key/attribute will match the name of the field. Note: This should only be used for very specific use cases such as outputting multiple fields for a single attribute, or using keys/attributes that are invalid variable names, unsuitable for field names. In most cases, you should use ``data_key`` instead. :param validate: Validator or collection of validators that are called during deserialization. Validator takes a field's input value as its only parameter and returns a boolean. If it returns `False`, an :exc:`ValidationError` is raised. :param required: Raise a :exc:`ValidationError` if the field value is not supplied during deserialization. :param allow_none: Set this to `True` if `None` should be considered a valid value during validation/deserialization. If set to `False` (the default), `None` is considered invalid input. If ``load_default`` is explicitly set to `None` and ``allow_none`` is unset, `allow_none` is implicitly set to ``True``. :param load_only: If `True` skip this field during serialization, otherwise its value will be present in the serialized data. :param dump_only: If `True` skip this field during deserialization, otherwise its value will be present in the deserialized object. In the context of an HTTP API, this effectively marks the field as "read-only". :param error_messages: Overrides for `Field.default_error_messages`. :param metadata: Extra information to be stored as field metadata. .. versionchanged:: 3.0.0b8 Add ``data_key`` parameter for the specifying the key in the input and output data. This parameter replaced both ``load_from`` and ``dump_to``. .. versionchanged:: 3.13.0 Replace ``missing`` and ``default`` parameters with ``load_default`` and ``dump_default``. .. versionchanged:: 3.24.0 `Field ` should no longer be used as a field within a `Schema `. Use `Raw ` or another `Field ` subclass instead. """ # Some fields, such as Method fields and Function fields, are not expected # to exist as attributes on the objects to serialize. Set this to False # for those fields _CHECK_ATTRIBUTE = True #: Default error messages for various kinds of errors. The keys in this dictionary #: are passed to `Field.make_error`. The values are error messages passed to #: :exc:`marshmallow.exceptions.ValidationError`. default_error_messages: dict[str, str] = { "required": "Missing data for required field.", "null": "Field may not be null.", "validator_failed": "Invalid value.", } def __init__( self, *, load_default: typing.Any = missing_, missing: typing.Any = missing_, dump_default: typing.Any = missing_, default: typing.Any = missing_, data_key: str | None = None, attribute: str | None = None, validate: types.Validator | typing.Iterable[types.Validator] | None = None, required: bool = False, allow_none: bool | None = None, load_only: bool = False, dump_only: bool = False, error_messages: dict[str, str] | None = None, metadata: typing.Mapping[str, typing.Any] | None = None, **additional_metadata, ) -> None: if self.__class__ is Field: warnings.warn( "`Field` should not be instantiated. Use `fields.Raw` or " "another field subclass instead.", ChangedInMarshmallow4Warning, stacklevel=2, ) # handle deprecated `default` and `missing` parameters if default is not missing_: warnings.warn( "The 'default' argument to fields is deprecated. " "Use 'dump_default' instead.", RemovedInMarshmallow4Warning, stacklevel=2, ) if dump_default is missing_: dump_default = default if missing is not missing_: warnings.warn( "The 'missing' argument to fields is deprecated. " "Use 'load_default' instead.", RemovedInMarshmallow4Warning, stacklevel=2, ) if load_default is missing_: load_default = missing self.dump_default = dump_default self.load_default = load_default self.attribute = attribute self.data_key = data_key self.validate = validate if validate is None: self.validators = [] elif callable(validate): self.validators = [validate] elif utils.is_iterable_but_not_string(validate): self.validators = list(validate) else: raise ValueError( "The 'validate' parameter must be a callable " "or a collection of callables." ) # If allow_none is None and load_default is None # None should be considered valid by default self.allow_none = load_default is None if allow_none is None else allow_none self.load_only = load_only self.dump_only = dump_only if required is True and load_default is not missing_: raise ValueError("'load_default' must not be set for required fields.") self.required = required metadata = metadata or {} self.metadata = {**metadata, **additional_metadata} if additional_metadata: warnings.warn( "Passing field metadata as keyword arguments is deprecated. Use the " "explicit `metadata=...` argument instead. " f"Additional metadata: {additional_metadata}", RemovedInMarshmallow4Warning, stacklevel=2, ) # Collect default error message from self and parent classes messages: dict[str, str] = {} for cls in reversed(self.__class__.__mro__): messages.update(getattr(cls, "default_error_messages", {})) messages.update(error_messages or {}) self.error_messages = messages self.parent: Field | Schema | None = None self.name: str | None = None self.root: Schema | None = None def __repr__(self) -> str: return ( f"" ) def __deepcopy__(self, memo): return copy.copy(self) def get_value( self, obj: typing.Any, attr: str, accessor: ( typing.Callable[[typing.Any, str, typing.Any], typing.Any] | None ) = None, default: typing.Any = missing_, ): """Return the value for a given key from an object. :param obj: The object to get the value from. :param attr: The attribute/key in `obj` to get the value from. :param accessor: A callable used to retrieve the value of `attr` from the object `obj`. Defaults to `marshmallow.utils.get_value`. """ accessor_func = accessor or utils.get_value check_key = attr if self.attribute is None else self.attribute return accessor_func(obj, check_key, default) def _validate(self, value: typing.Any): """Perform validation on ``value``. Raise a :exc:`ValidationError` if validation does not succeed. """ self._validate_all(value) @property def _validate_all(self) -> typing.Callable[[typing.Any], None]: return And(*self.validators, error=self.error_messages["validator_failed"]) def make_error(self, key: str, **kwargs) -> ValidationError: """Helper method to make a `ValidationError` with an error message from ``self.error_messages``. """ try: msg = self.error_messages[key] except KeyError as error: class_name = self.__class__.__name__ message = ( f"ValidationError raised by `{class_name}`, but error key `{key}` does " "not exist in the `error_messages` dictionary." ) raise AssertionError(message) from error if isinstance(msg, (str, bytes)): msg = msg.format(**kwargs) return ValidationError(msg) def fail(self, key: str, **kwargs): """Helper method that raises a `ValidationError` with an error message from ``self.error_messages``. .. deprecated:: 3.0.0 Use `make_error ` instead. """ warnings.warn( f'`Field.fail` is deprecated. Use `raise self.make_error("{key}", ...)` instead.', RemovedInMarshmallow4Warning, stacklevel=2, ) raise self.make_error(key=key, **kwargs) def _validate_missing(self, value: typing.Any) -> None: """Validate missing values. Raise a :exc:`ValidationError` if `value` should be considered missing. """ if value is missing_ and self.required: raise self.make_error("required") if value is None and not self.allow_none: raise self.make_error("null") def serialize( self, attr: str, obj: typing.Any, accessor: ( typing.Callable[[typing.Any, str, typing.Any], typing.Any] | None ) = None, **kwargs, ): """Pulls the value for the given key from the object, applies the field's formatting and returns the result. :param attr: The attribute/key to get from the object. :param obj: The object to access the attribute/key from. :param accessor: Function used to access values from ``obj``. :param kwargs: Field-specific keyword arguments. """ if self._CHECK_ATTRIBUTE: value = self.get_value(obj, attr, accessor=accessor) if value is missing_: default = self.dump_default value = default() if callable(default) else default if value is missing_: return value else: value = None return self._serialize(value, attr, obj, **kwargs) def deserialize( self, value: typing.Any, attr: str | None = None, data: typing.Mapping[str, typing.Any] | None = None, **kwargs, ): """Deserialize ``value``. :param value: The value to deserialize. :param attr: The attribute/key in `data` to deserialize. :param data: The raw input data passed to `Schema.load `. :param kwargs: Field-specific keyword arguments. :raise ValidationError: If an invalid value is passed or if a required value is missing. """ # Validate required fields, deserialize, then validate # deserialized value self._validate_missing(value) if value is missing_: _miss = self.load_default return _miss() if callable(_miss) else _miss if self.allow_none and value is None: return None output = self._deserialize(value, attr, data, **kwargs) self._validate(output) return output # Methods for concrete classes to override. def _bind_to_schema(self, field_name: str, schema: Schema | Field) -> None: """Update field with values from its parent schema. Called by `Schema._bind_field `. :param field_name: Field name set in schema. :param schema: Parent object. """ self.parent = self.parent or schema self.name = self.name or field_name self.root = self.root or ( self.parent.root if isinstance(self.parent, FieldABC) else self.parent ) def _serialize( self, value: typing.Any, attr: str | None, obj: typing.Any, **kwargs ) -> typing.Any: """Serializes ``value`` to a basic Python datatype. Noop by default. Concrete :class:`Field` classes should implement this method. Example: :: class TitleCase(Field): def _serialize(self, value, attr, obj, **kwargs): if not value: return "" return str(value).title() :param value: The value to be serialized. :param attr: The attribute or key on the object to be serialized. :param obj: The object the value was pulled from. :param kwargs: Field-specific keyword arguments. :return: The serialized value """ return value def _deserialize( self, value: typing.Any, attr: str | None, data: typing.Mapping[str, typing.Any] | None, **kwargs, ) -> typing.Any: """Deserialize value. Concrete :class:`Field` classes should implement this method. :param value: The value to be deserialized. :param attr: The attribute/key in `data` to be deserialized. :param data: The raw input data passed to the `Schema.load `. :param kwargs: Field-specific keyword arguments. :raise ValidationError: In case of formatting or validation failure. :return: The deserialized value. .. versionchanged:: 3.0.0 Added ``**kwargs`` to signature. """ return value # Properties @property def context(self) -> dict | None: """The context dictionary for the parent `Schema `.""" if self.parent: return self.parent.context return None # the default and missing properties are provided for compatibility and # emit warnings when they are accessed and set @property def default(self): warnings.warn( "The 'default' attribute of fields is deprecated. " "Use 'dump_default' instead.", RemovedInMarshmallow4Warning, stacklevel=2, ) return self.dump_default @default.setter def default(self, value): warnings.warn( "The 'default' attribute of fields is deprecated. " "Use 'dump_default' instead.", RemovedInMarshmallow4Warning, stacklevel=2, ) self.dump_default = value @property def missing(self): warnings.warn( "The 'missing' attribute of fields is deprecated. " "Use 'load_default' instead.", RemovedInMarshmallow4Warning, stacklevel=2, ) return self.load_default @missing.setter def missing(self, value): warnings.warn( "The 'missing' attribute of fields is deprecated. " "Use 'load_default' instead.", RemovedInMarshmallow4Warning, stacklevel=2, ) self.load_default = value class Raw(Field): """Field that applies no formatting.""" class Nested(Field): """Allows you to nest a :class:`Schema ` inside a field. Examples: :: class ChildSchema(Schema): id = fields.Str() name = fields.Str() # Use lambda functions when you need two-way nesting or self-nesting parent = fields.Nested(lambda: ParentSchema(only=("id",)), dump_only=True) siblings = fields.List( fields.Nested(lambda: ChildSchema(only=("id", "name"))) ) class ParentSchema(Schema): id = fields.Str() children = fields.List( fields.Nested(ChildSchema(only=("id", "parent", "siblings"))) ) spouse = fields.Nested(lambda: ParentSchema(only=("id",))) When passing a `Schema ` instance as the first argument, the instance's ``exclude``, ``only``, and ``many`` attributes will be respected. Therefore, when passing the ``exclude``, ``only``, or ``many`` arguments to `fields.Nested`, you should pass a `Schema ` class (not an instance) as the first argument. :: # Yes author = fields.Nested(UserSchema, only=("id", "name")) # No author = fields.Nested(UserSchema(), only=("id", "name")) :param nested: `Schema ` instance, class, class name (string), dictionary, or callable that returns a `Schema ` or dictionary. Dictionaries are converted with `Schema.from_dict `. :param exclude: A list or tuple of fields to exclude. :param only: A list or tuple of fields to marshal. If `None`, all fields are marshalled. This parameter takes precedence over ``exclude``. :param many: Whether the field is a collection of objects. :param unknown: Whether to exclude, include, or raise an error for unknown fields in the data. Use `EXCLUDE`, `INCLUDE` or `RAISE`. :param kwargs: The same keyword arguments that :class:`Field` receives. """ #: Default error messages. default_error_messages = {"type": "Invalid type."} def __init__( self, nested: ( Schema | SchemaMeta | str | dict[str, Field] | typing.Callable[[], Schema | SchemaMeta | dict[str, Field]] ), *, dump_default: typing.Any = missing_, default: typing.Any = missing_, only: types.StrSequenceOrSet | None = None, exclude: types.StrSequenceOrSet = (), many: bool = False, unknown: str | None = None, **kwargs, ): # Raise error if only or exclude is passed as string, not list of strings if only is not None and not is_collection(only): raise StringNotCollectionError('"only" should be a collection of strings.') if not is_collection(exclude): raise StringNotCollectionError( '"exclude" should be a collection of strings.' ) if nested == "self": warnings.warn( "Passing 'self' to `Nested` is deprecated. " "Use `Nested(lambda: MySchema(...))` instead.", RemovedInMarshmallow4Warning, stacklevel=2, ) self.nested = nested self.only = only self.exclude = exclude self.many = many self.unknown = unknown self._schema: Schema | None = None # Cached Schema instance super().__init__(default=default, dump_default=dump_default, **kwargs) @property def schema(self) -> Schema: """The nested `Schema ` object. .. versionchanged:: 1.0.0 Renamed from ``serializer`` to ``schema``. """ if not self._schema: # Inherit context from parent. context = getattr(self.parent, "context", {}) if callable(self.nested) and not isinstance(self.nested, type): nested = self.nested() else: nested = typing.cast("Schema", self.nested) # defer the import of `marshmallow.schema` to avoid circular imports from marshmallow.schema import Schema if isinstance(nested, dict): nested = Schema.from_dict(nested) if isinstance(nested, Schema): self._schema = copy.copy(nested) self._schema.context.update(context) # Respect only and exclude passed from parent and re-initialize fields set_class = typing.cast(type[set], self._schema.set_class) if self.only is not None: if self._schema.only is not None: original = self._schema.only else: # only=None -> all fields original = self._schema.fields.keys() self._schema.only = set_class(self.only) & set_class(original) if self.exclude: original = self._schema.exclude self._schema.exclude = set_class(self.exclude) | set_class(original) self._schema._init_fields() else: if isinstance(nested, type) and issubclass(nested, Schema): schema_class: type[Schema] = nested elif not isinstance(nested, (str, bytes)): raise ValueError( "`Nested` fields must be passed a " f"`Schema`, not {nested.__class__}." ) elif nested == "self": schema_class = typing.cast(Schema, self.root).__class__ else: schema_class = class_registry.get_class(nested, all=False) self._schema = schema_class( many=self.many, only=self.only, exclude=self.exclude, context=context, load_only=self._nested_normalized_option("load_only"), dump_only=self._nested_normalized_option("dump_only"), ) return self._schema def _nested_normalized_option(self, option_name: str) -> list[str]: nested_field = f"{self.name}." return [ field.split(nested_field, 1)[1] for field in getattr(self.root, option_name, set()) if field.startswith(nested_field) ] def _serialize(self, nested_obj, attr, obj, **kwargs): # Load up the schema first. This allows a RegistryError to be raised # if an invalid schema name was passed schema = self.schema if nested_obj is None: return None many = schema.many or self.many return schema.dump(nested_obj, many=many) def _test_collection(self, value: typing.Any) -> None: many = self.schema.many or self.many if many and not utils.is_collection(value): raise self.make_error("type", input=value, type=value.__class__.__name__) def _load( self, value: typing.Any, partial: bool | types.StrSequenceOrSet | None = None ): try: valid_data = self.schema.load(value, unknown=self.unknown, partial=partial) except ValidationError as error: raise ValidationError( error.messages, valid_data=error.valid_data ) from error return valid_data def _deserialize( self, value: typing.Any, attr: str | None, data: typing.Mapping[str, typing.Any] | None, partial: bool | types.StrSequenceOrSet | None = None, **kwargs, ) -> typing.Any: """Same as :meth:`Field._deserialize` with additional ``partial`` argument. :param partial: For nested schemas, the ``partial`` parameter passed to `marshmallow.Schema.load`. .. versionchanged:: 3.0.0 Add ``partial`` parameter. """ self._test_collection(value) return self._load(value, partial=partial) class Pluck(Nested): """Allows you to replace nested data with one of the data's fields. Example: :: from marshmallow import Schema, fields class ArtistSchema(Schema): id = fields.Int() name = fields.Str() class AlbumSchema(Schema): artist = fields.Pluck(ArtistSchema, "id") in_data = {"artist": 42} loaded = AlbumSchema().load(in_data) # => {'artist': {'id': 42}} dumped = AlbumSchema().dump(loaded) # => {'artist': 42} :param nested: The Schema class or class name (string) to nest, or ``"self"`` to nest the `Schema ` within itself. :param field_name: The key to pluck a value from. :param kwargs: The same keyword arguments that :class:`Nested` receives. """ def __init__( self, nested: Schema | SchemaMeta | str | typing.Callable[[], Schema], field_name: str, *, many: bool = False, unknown: str | None = None, **kwargs, ): super().__init__( nested, only=(field_name,), many=many, unknown=unknown, **kwargs ) self.field_name = field_name @property def _field_data_key(self) -> str: only_field = self.schema.fields[self.field_name] return only_field.data_key or self.field_name def _serialize(self, nested_obj, attr, obj, **kwargs): ret = super()._serialize(nested_obj, attr, obj, **kwargs) if ret is None: return None if self.many: return utils.pluck(ret, key=self._field_data_key) return ret[self._field_data_key] def _deserialize(self, value, attr, data, partial=None, **kwargs): self._test_collection(value) if self.many: value = [{self._field_data_key: v} for v in value] else: value = {self._field_data_key: value} return self._load(value, partial=partial) class List(Field): """A list field, composed with another `Field` class or instance. Example: :: numbers = fields.List(fields.Float()) :param cls_or_instance: A field class or instance. :param kwargs: The same keyword arguments that :class:`Field` receives. .. versionchanged:: 3.0.0rc9 Does not serialize scalar values to single-item lists. """ #: Default error messages. default_error_messages = {"invalid": "Not a valid list."} def __init__(self, cls_or_instance: Field | type[Field], **kwargs): super().__init__(**kwargs) try: self.inner = resolve_field_instance(cls_or_instance) except FieldInstanceResolutionError as error: raise ValueError( "The list elements must be a subclass or instance of " "marshmallow.base.FieldABC." ) from error if isinstance(self.inner, Nested): self.only = self.inner.only self.exclude = self.inner.exclude def _bind_to_schema(self, field_name: str, schema: Schema | Field) -> None: super()._bind_to_schema(field_name, schema) self.inner = copy.deepcopy(self.inner) self.inner._bind_to_schema(field_name, self) if isinstance(self.inner, Nested): self.inner.only = self.only self.inner.exclude = self.exclude def _serialize(self, value, attr, obj, **kwargs) -> list[typing.Any] | None: if value is None: return None return [self.inner._serialize(each, attr, obj, **kwargs) for each in value] def _deserialize(self, value, attr, data, **kwargs) -> list[typing.Any]: if not utils.is_collection(value): raise self.make_error("invalid") result = [] errors = {} for idx, each in enumerate(value): try: result.append(self.inner.deserialize(each, **kwargs)) except ValidationError as error: if error.valid_data is not None: result.append(error.valid_data) errors.update({idx: error.messages}) if errors: raise ValidationError(errors, valid_data=result) return result class Tuple(Field): """A tuple field, composed of a fixed number of other `Field` classes or instances Example: :: row = Tuple((fields.String(), fields.Integer(), fields.Float())) .. note:: Because of the structured nature of `collections.namedtuple` and `typing.NamedTuple`, using a Schema within a Nested field for them is more appropriate than using a `Tuple` field. :param tuple_fields: An iterable of field classes or instances. :param kwargs: The same keyword arguments that :class:`Field` receives. .. versionadded:: 3.0.0rc4 """ #: Default error messages. default_error_messages = {"invalid": "Not a valid tuple."} def __init__( self, tuple_fields: typing.Iterable[Field] | typing.Iterable[type[Field]], **kwargs, ): super().__init__(**kwargs) if not utils.is_collection(tuple_fields): raise ValueError( "tuple_fields must be an iterable of Field classes or instances." ) try: self.tuple_fields = [ resolve_field_instance(cls_or_instance) for cls_or_instance in tuple_fields ] except FieldInstanceResolutionError as error: raise ValueError( 'Elements of "tuple_fields" must be subclasses or ' "instances of marshmallow.base.FieldABC." ) from error self.validate_length = Length(equal=len(self.tuple_fields)) def _bind_to_schema(self, field_name: str, schema: Schema | Field) -> None: super()._bind_to_schema(field_name, schema) new_tuple_fields = [] for field in self.tuple_fields: new_field = copy.deepcopy(field) new_field._bind_to_schema(field_name, self) new_tuple_fields.append(new_field) self.tuple_fields = new_tuple_fields def _serialize(self, value, attr, obj, **kwargs) -> tuple | None: if value is None: return None return tuple( field._serialize(each, attr, obj, **kwargs) for field, each in zip(self.tuple_fields, value) ) def _deserialize(self, value, attr, data, **kwargs) -> tuple: if not utils.is_collection(value): raise self.make_error("invalid") self.validate_length(value) result = [] errors = {} for idx, (field, each) in enumerate(zip(self.tuple_fields, value)): try: result.append(field.deserialize(each, **kwargs)) except ValidationError as error: if error.valid_data is not None: result.append(error.valid_data) errors.update({idx: error.messages}) if errors: raise ValidationError(errors, valid_data=result) return tuple(result) class String(Field): """A string field. :param kwargs: The same keyword arguments that :class:`Field` receives. """ #: Default error messages. default_error_messages = { "invalid": "Not a valid string.", "invalid_utf8": "Not a valid utf-8 string.", } def _serialize(self, value, attr, obj, **kwargs) -> str | None: if value is None: return None return utils.ensure_text_type(value) def _deserialize(self, value, attr, data, **kwargs) -> typing.Any: if not isinstance(value, (str, bytes)): raise self.make_error("invalid") try: return utils.ensure_text_type(value) except UnicodeDecodeError as error: raise self.make_error("invalid_utf8") from error class UUID(String): """A UUID field.""" #: Default error messages. default_error_messages = {"invalid_uuid": "Not a valid UUID."} def _validated(self, value) -> uuid.UUID | None: """Format the value or raise a :exc:`ValidationError` if an error occurs.""" if value is None: return None if isinstance(value, uuid.UUID): return value try: if isinstance(value, bytes) and len(value) == 16: return uuid.UUID(bytes=value) return uuid.UUID(value) except (ValueError, AttributeError, TypeError) as error: raise self.make_error("invalid_uuid") from error def _deserialize(self, value, attr, data, **kwargs) -> uuid.UUID | None: return self._validated(value) _NumType = typing.TypeVar("_NumType") class Number(Field, typing.Generic[_NumType]): """Base class for number fields. :param as_string: If `True`, format the serialized value as a string. :param kwargs: The same keyword arguments that :class:`Field` receives. .. versionchanged:: 3.24.0 `Number ` should no longer be used as a field within a `Schema `. Use `Integer `, `Float `, or `Decimal ` instead. """ num_type: type = float #: Default error messages. default_error_messages = { "invalid": "Not a valid number.", "too_large": "Number too large.", } def __init__(self, *, as_string: bool = False, **kwargs): if self.__class__ is Number: warnings.warn( "`Number` field should not be instantiated. Use `Integer`, `Float`, or `Decimal` instead.", ChangedInMarshmallow4Warning, stacklevel=2, ) self.as_string = as_string super().__init__(**kwargs) def _format_num(self, value) -> _NumType: """Return the number value for value, given this field's `num_type`.""" return self.num_type(value) def _validated(self, value: typing.Any) -> _NumType: """Format the value or raise a :exc:`ValidationError` if an error occurs.""" # (value is True or value is False) is ~5x faster than isinstance(value, bool) if value is True or value is False: raise self.make_error("invalid", input=value) try: return self._format_num(value) except (TypeError, ValueError) as error: raise self.make_error("invalid", input=value) from error except OverflowError as error: raise self.make_error("too_large", input=value) from error def _to_string(self, value: _NumType) -> str: return str(value) def _serialize(self, value, attr, obj, **kwargs) -> str | _NumType | None: """Return a string if `self.as_string=True`, otherwise return this field's `num_type`.""" if value is None: return None ret: _NumType = self._format_num(value) return self._to_string(ret) if self.as_string else ret def _deserialize(self, value, attr, data, **kwargs) -> _NumType | None: return self._validated(value) class Integer(Number[int]): """An integer field. :param strict: If `True`, only integer types are valid. Otherwise, any value castable to `int` is valid. :param kwargs: The same keyword arguments that :class:`Number` receives. """ num_type = int #: Default error messages. default_error_messages = {"invalid": "Not a valid integer."} def __init__(self, *, strict: bool = False, **kwargs): self.strict = strict super().__init__(**kwargs) # override Number def _validated(self, value: typing.Any) -> int: if self.strict and not isinstance(value, numbers.Integral): raise self.make_error("invalid", input=value) return super()._validated(value) class Float(Number[float]): """A double as an IEEE-754 double precision string. :param allow_nan: If `True`, `NaN`, `Infinity` and `-Infinity` are allowed, even though they are illegal according to the JSON specification. :param as_string: If `True`, format the value as a string. :param kwargs: The same keyword arguments that :class:`Number` receives. """ num_type = float #: Default error messages. default_error_messages = { "special": "Special numeric values (nan or infinity) are not permitted." } def __init__(self, *, allow_nan: bool = False, as_string: bool = False, **kwargs): self.allow_nan = allow_nan super().__init__(as_string=as_string, **kwargs) def _validated(self, value: typing.Any) -> float: num = super()._validated(value) if self.allow_nan is False: if math.isnan(num) or num == float("inf") or num == float("-inf"): raise self.make_error("special") return num class Decimal(Number[decimal.Decimal]): """A field that (de)serializes to the Python ``decimal.Decimal`` type. It's safe to use when dealing with money values, percentages, ratios or other numbers where precision is critical. .. warning:: This field serializes to a `decimal.Decimal` object by default. If you need to render your data as JSON, keep in mind that the `json` module from the standard library does not encode `decimal.Decimal`. Therefore, you must use a JSON library that can handle decimals, such as `simplejson`, or serialize to a string by passing ``as_string=True``. .. warning:: If a JSON `float` value is passed to this field for deserialization it will first be cast to its corresponding `string` value before being deserialized to a `decimal.Decimal` object. The default `__str__` implementation of the built-in Python `float` type may apply a destructive transformation upon its input data and therefore cannot be relied upon to preserve precision. To avoid this, you can instead pass a JSON `string` to be deserialized directly. :param places: How many decimal places to quantize the value. If `None`, does not quantize the value. :param rounding: How to round the value during quantize, for example `decimal.ROUND_UP`. If `None`, uses the rounding value from the current thread's context. :param allow_nan: If `True`, `NaN`, `Infinity` and `-Infinity` are allowed, even though they are illegal according to the JSON specification. :param as_string: If `True`, serialize to a string instead of a Python `decimal.Decimal` type. :param kwargs: The same keyword arguments that :class:`Number` receives. .. versionadded:: 1.2.0 """ num_type = decimal.Decimal #: Default error messages. default_error_messages = { "special": "Special numeric values (nan or infinity) are not permitted." } def __init__( self, places: int | None = None, rounding: str | None = None, *, allow_nan: bool = False, as_string: bool = False, **kwargs, ): self.places = ( decimal.Decimal((0, (1,), -places)) if places is not None else None ) self.rounding = rounding self.allow_nan = allow_nan super().__init__(as_string=as_string, **kwargs) # override Number def _format_num(self, value): num = decimal.Decimal(str(value)) if self.allow_nan: if num.is_nan(): return decimal.Decimal("NaN") # avoid sNaN, -sNaN and -NaN if self.places is not None and num.is_finite(): num = num.quantize(self.places, rounding=self.rounding) return num # override Number def _validated(self, value: typing.Any) -> decimal.Decimal: try: num = super()._validated(value) except decimal.InvalidOperation as error: raise self.make_error("invalid") from error if not self.allow_nan and (num.is_nan() or num.is_infinite()): raise self.make_error("special") return num # override Number def _to_string(self, value: decimal.Decimal) -> str: return format(value, "f") class Boolean(Field): """A boolean field. :param truthy: Values that will (de)serialize to `True`. If an empty set, any non-falsy value will deserialize to `True`. If `None`, `marshmallow.fields.Boolean.truthy` will be used. :param falsy: Values that will (de)serialize to `False`. If `None`, `marshmallow.fields.Boolean.falsy` will be used. :param kwargs: The same keyword arguments that :class:`Field` receives. """ #: Default truthy values. truthy = { "t", "T", "true", "True", "TRUE", "on", "On", "ON", "y", "Y", "yes", "Yes", "YES", "1", 1, # Equal to 1 # True, } #: Default falsy values. falsy = { "f", "F", "false", "False", "FALSE", "off", "Off", "OFF", "n", "N", "no", "No", "NO", "0", 0, # Equal to 0 # 0.0, # False, } #: Default error messages. default_error_messages = {"invalid": "Not a valid boolean."} def __init__( self, *, truthy: typing.Iterable | None = None, falsy: typing.Iterable | None = None, **kwargs, ): super().__init__(**kwargs) if truthy is not None: self.truthy = set(truthy) if falsy is not None: self.falsy = set(falsy) def _serialize( self, value: typing.Any, attr: str | None, obj: typing.Any, **kwargs ): if value is None: return None try: if value in self.truthy: return True if value in self.falsy: return False except TypeError: pass return bool(value) def _deserialize(self, value, attr, data, **kwargs): if not self.truthy: return bool(value) try: if value in self.truthy: return True if value in self.falsy: return False except TypeError as error: raise self.make_error("invalid", input=value) from error raise self.make_error("invalid", input=value) class DateTime(Field): """A formatted datetime string. Example: ``'2014-12-22T03:12:58.019077+00:00'`` :param format: Either ``"rfc"`` (for RFC822), ``"iso"`` (for ISO8601), ``"timestamp"``, ``"timestamp_ms"`` (for a POSIX timestamp) or a date format string. If `None`, defaults to "iso". :param kwargs: The same keyword arguments that :class:`Field` receives. .. versionchanged:: 3.0.0rc9 Does not modify timezone information on (de)serialization. .. versionchanged:: 3.19 Add timestamp as a format. """ SERIALIZATION_FUNCS: dict[str, typing.Callable[[typing.Any], str | float]] = { "iso": utils.isoformat, "iso8601": utils.isoformat, "rfc": utils.rfcformat, "rfc822": utils.rfcformat, "timestamp": utils.timestamp, "timestamp_ms": utils.timestamp_ms, } DESERIALIZATION_FUNCS: dict[str, typing.Callable[[str], typing.Any]] = { "iso": utils.from_iso_datetime, "iso8601": utils.from_iso_datetime, "rfc": utils.from_rfc, "rfc822": utils.from_rfc, "timestamp": utils.from_timestamp, "timestamp_ms": utils.from_timestamp_ms, } DEFAULT_FORMAT = "iso" OBJ_TYPE = "datetime" SCHEMA_OPTS_VAR_NAME = "datetimeformat" #: Default error messages. default_error_messages = { "invalid": "Not a valid {obj_type}.", "invalid_awareness": "Not a valid {awareness} {obj_type}.", "format": '"{input}" cannot be formatted as a {obj_type}.', } def __init__(self, format: str | None = None, **kwargs) -> None: # noqa: A002 super().__init__(**kwargs) # Allow this to be None. It may be set later in the ``_serialize`` # or ``_deserialize`` methods. This allows a Schema to dynamically set the # format, e.g. from a Meta option self.format = format def _bind_to_schema(self, field_name, schema): super()._bind_to_schema(field_name, schema) self.format = ( self.format or getattr(self.root.opts, self.SCHEMA_OPTS_VAR_NAME) or self.DEFAULT_FORMAT ) def _serialize(self, value, attr, obj, **kwargs) -> str | float | None: if value is None: return None data_format = self.format or self.DEFAULT_FORMAT format_func = self.SERIALIZATION_FUNCS.get(data_format) if format_func: return format_func(value) return value.strftime(data_format) def _deserialize(self, value, attr, data, **kwargs) -> dt.datetime: data_format = self.format or self.DEFAULT_FORMAT func = self.DESERIALIZATION_FUNCS.get(data_format) try: if func: return func(value) return self._make_object_from_format(value, data_format) except (TypeError, AttributeError, ValueError) as error: raise self.make_error( "invalid", input=value, obj_type=self.OBJ_TYPE ) from error @staticmethod def _make_object_from_format(value, data_format) -> dt.datetime: return dt.datetime.strptime(value, data_format) class NaiveDateTime(DateTime): """A formatted naive datetime string. :param format: See :class:`DateTime`. :param timezone: Used on deserialization. If `None`, aware datetimes are rejected. If not `None`, aware datetimes are converted to this timezone before their timezone information is removed. :param kwargs: The same keyword arguments that :class:`Field` receives. .. versionadded:: 3.0.0rc9 """ AWARENESS = "naive" def __init__( self, format: str | None = None, # noqa: A002 *, timezone: dt.timezone | None = None, **kwargs, ) -> None: super().__init__(format=format, **kwargs) self.timezone = timezone def _deserialize(self, value, attr, data, **kwargs) -> dt.datetime: ret = super()._deserialize(value, attr, data, **kwargs) if is_aware(ret): if self.timezone is None: raise self.make_error( "invalid_awareness", awareness=self.AWARENESS, obj_type=self.OBJ_TYPE, ) ret = ret.astimezone(self.timezone).replace(tzinfo=None) return ret class AwareDateTime(DateTime): """A formatted aware datetime string. :param format: See :class:`DateTime`. :param default_timezone: Used on deserialization. If `None`, naive datetimes are rejected. If not `None`, naive datetimes are set this timezone. :param kwargs: The same keyword arguments that :class:`Field` receives. .. versionadded:: 3.0.0rc9 """ AWARENESS = "aware" def __init__( self, format: str | None = None, # noqa: A002 *, default_timezone: dt.tzinfo | None = None, **kwargs, ) -> None: super().__init__(format=format, **kwargs) self.default_timezone = default_timezone def _deserialize(self, value, attr, data, **kwargs) -> dt.datetime: ret = super()._deserialize(value, attr, data, **kwargs) if not is_aware(ret): if self.default_timezone is None: raise self.make_error( "invalid_awareness", awareness=self.AWARENESS, obj_type=self.OBJ_TYPE, ) ret = ret.replace(tzinfo=self.default_timezone) return ret class Time(DateTime): """A formatted time string. Example: ``'03:12:58.019077'`` :param format: Either ``"iso"`` (for ISO8601) or a date format string. If `None`, defaults to "iso". :param kwargs: The same keyword arguments that :class:`Field` receives. """ SERIALIZATION_FUNCS = {"iso": utils.to_iso_time, "iso8601": utils.to_iso_time} DESERIALIZATION_FUNCS = {"iso": utils.from_iso_time, "iso8601": utils.from_iso_time} DEFAULT_FORMAT = "iso" OBJ_TYPE = "time" SCHEMA_OPTS_VAR_NAME = "timeformat" @staticmethod def _make_object_from_format(value, data_format): return dt.datetime.strptime(value, data_format).time() class Date(DateTime): """ISO8601-formatted date string. :param format: Either ``"iso"`` (for ISO8601) or a date format string. If `None`, defaults to "iso". :param kwargs: The same keyword arguments that :class:`Field` receives. """ #: Default error messages. default_error_messages = { "invalid": "Not a valid date.", "format": '"{input}" cannot be formatted as a date.', } SERIALIZATION_FUNCS = {"iso": utils.to_iso_date, "iso8601": utils.to_iso_date} DESERIALIZATION_FUNCS = {"iso": utils.from_iso_date, "iso8601": utils.from_iso_date} DEFAULT_FORMAT = "iso" OBJ_TYPE = "date" SCHEMA_OPTS_VAR_NAME = "dateformat" @staticmethod def _make_object_from_format(value, data_format): return dt.datetime.strptime(value, data_format).date() class TimeDelta(Field): """A field that (de)serializes a :class:`datetime.timedelta` object to an integer or float and vice versa. The integer or float can represent the number of days, seconds or microseconds. :param precision: Influences how the integer or float is interpreted during (de)serialization. Must be 'days', 'seconds', 'microseconds', 'milliseconds', 'minutes', 'hours' or 'weeks'. :param serialization_type: Whether to (de)serialize to a `int` or `float`. :param kwargs: The same keyword arguments that :class:`Field` receives. Integer Caveats --------------- Any fractional parts (which depends on the precision used) will be truncated when serializing using `int`. Float Caveats ------------- Use of `float` when (de)serializing may result in data precision loss due to the way machines handle floating point values. Regardless of the precision chosen, the fractional part when using `float` will always be truncated to microseconds. For example, `1.12345` interpreted as microseconds will result in `timedelta(microseconds=1)`. .. versionchanged:: 3.17.0 Allow (de)serialization to `float` through use of a new `serialization_type` parameter. `int` is the default to retain previous behaviour. """ DAYS = "days" SECONDS = "seconds" MICROSECONDS = "microseconds" MILLISECONDS = "milliseconds" MINUTES = "minutes" HOURS = "hours" WEEKS = "weeks" #: Default error messages. default_error_messages = { "invalid": "Not a valid period of time.", "format": "{input!r} cannot be formatted as a timedelta.", } def __init__( self, precision: str = SECONDS, serialization_type: type[int | float] = int, **kwargs, ): precision = precision.lower() units = ( self.DAYS, self.SECONDS, self.MICROSECONDS, self.MILLISECONDS, self.MINUTES, self.HOURS, self.WEEKS, ) if precision not in units: msg = 'The precision must be {} or "{}".'.format( ", ".join([f'"{each}"' for each in units[:-1]]), units[-1] ) raise ValueError(msg) if serialization_type not in (int, float): raise ValueError("The serialization type must be one of int or float") self.precision = precision self.serialization_type = serialization_type super().__init__(**kwargs) def _serialize(self, value, attr, obj, **kwargs): if value is None: return None base_unit = dt.timedelta(**{self.precision: 1}) if self.serialization_type is int: delta = utils.timedelta_to_microseconds(value) unit = utils.timedelta_to_microseconds(base_unit) return delta // unit assert self.serialization_type is float # noqa: S101 return value.total_seconds() / base_unit.total_seconds() def _deserialize(self, value, attr, data, **kwargs): try: value = self.serialization_type(value) except (TypeError, ValueError) as error: raise self.make_error("invalid") from error kwargs = {self.precision: value} try: return dt.timedelta(**kwargs) except OverflowError as error: raise self.make_error("invalid") from error class Mapping(Field): """An abstract class for objects with key-value pairs. This class should not be used within schemas. :param keys: A field class or instance for dict keys. :param values: A field class or instance for dict values. :param kwargs: The same keyword arguments that :class:`Field` receives. .. note:: When the structure of nested data is not known, you may omit the `keys` and `values` arguments to prevent content validation. .. versionadded:: 3.0.0rc4 .. versionchanged:: 3.24.0 `Mapping ` should no longer be used as a field within a `Schema `. Use `Dict ` instead. """ mapping_type = dict #: Default error messages. default_error_messages = {"invalid": "Not a valid mapping type."} def __init__( self, keys: Field | type[Field] | None = None, values: Field | type[Field] | None = None, **kwargs, ): if self.__class__ is Mapping: warnings.warn( "`Mapping` field should not be instantiated. Use `Dict` instead.", ChangedInMarshmallow4Warning, stacklevel=2, ) super().__init__(**kwargs) if keys is None: self.key_field = None else: try: self.key_field = resolve_field_instance(keys) except FieldInstanceResolutionError as error: raise ValueError( '"keys" must be a subclass or instance of ' "marshmallow.base.FieldABC." ) from error if values is None: self.value_field = None else: try: self.value_field = resolve_field_instance(values) except FieldInstanceResolutionError as error: raise ValueError( '"values" must be a subclass or instance of ' "marshmallow.base.FieldABC." ) from error if isinstance(self.value_field, Nested): self.only = self.value_field.only self.exclude = self.value_field.exclude def _bind_to_schema(self, field_name, schema): super()._bind_to_schema(field_name, schema) if self.value_field: self.value_field = copy.deepcopy(self.value_field) self.value_field._bind_to_schema(field_name, self) if isinstance(self.value_field, Nested): self.value_field.only = self.only self.value_field.exclude = self.exclude if self.key_field: self.key_field = copy.deepcopy(self.key_field) self.key_field._bind_to_schema(field_name, self) def _serialize(self, value, attr, obj, **kwargs): if value is None: return None if not self.value_field and not self.key_field: return self.mapping_type(value) # Serialize keys if self.key_field is None: keys = {k: k for k in value} else: keys = { k: self.key_field._serialize(k, None, None, **kwargs) for k in value } # Serialize values result = self.mapping_type() if self.value_field is None: for k, v in value.items(): if k in keys: result[keys[k]] = v else: for k, v in value.items(): result[keys[k]] = self.value_field._serialize(v, None, None, **kwargs) return result def _deserialize(self, value, attr, data, **kwargs): if not isinstance(value, _Mapping): raise self.make_error("invalid") if not self.value_field and not self.key_field: return self.mapping_type(value) errors = collections.defaultdict(dict) # Deserialize keys if self.key_field is None: keys = {k: k for k in value} else: keys = {} for key in value: try: keys[key] = self.key_field.deserialize(key, **kwargs) except ValidationError as error: errors[key]["key"] = error.messages # Deserialize values result = self.mapping_type() if self.value_field is None: for k, v in value.items(): if k in keys: result[keys[k]] = v else: for key, val in value.items(): try: deser_val = self.value_field.deserialize(val, **kwargs) except ValidationError as error: errors[key]["value"] = error.messages if error.valid_data is not None and key in keys: result[keys[key]] = error.valid_data else: if key in keys: result[keys[key]] = deser_val if errors: raise ValidationError(errors, valid_data=result) return result class Dict(Mapping): """A dict field. Supports dicts and dict-like objects. Extends Mapping with dict as the mapping_type. Example: :: numbers = fields.Dict(keys=fields.Str(), values=fields.Float()) :param kwargs: The same keyword arguments that :class:`Mapping` receives. .. versionadded:: 2.1.0 """ mapping_type = dict class Url(String): """An URL field. :param default: Default value for the field if the attribute is not set. :param relative: Whether to allow relative URLs. :param absolute: Whether to allow absolute URLs. :param require_tld: Whether to reject non-FQDN hostnames. :param schemes: Valid schemes. By default, ``http``, ``https``, ``ftp``, and ``ftps`` are allowed. :param kwargs: The same keyword arguments that :class:`String` receives. """ #: Default error messages. default_error_messages = {"invalid": "Not a valid URL."} def __init__( self, *, relative: bool = False, absolute: bool = True, schemes: types.StrSequenceOrSet | None = None, require_tld: bool = True, **kwargs, ): super().__init__(**kwargs) self.relative = relative self.absolute = absolute self.require_tld = require_tld # Insert validation into self.validators so that multiple errors can be stored. validator = validate.URL( relative=self.relative, absolute=self.absolute, schemes=schemes, require_tld=self.require_tld, error=self.error_messages["invalid"], ) self.validators.insert(0, validator) class Email(String): """An email field. :param args: The same positional arguments that :class:`String` receives. :param kwargs: The same keyword arguments that :class:`String` receives. """ #: Default error messages. default_error_messages = {"invalid": "Not a valid email address."} def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) # Insert validation into self.validators so that multiple errors can be stored. validator = validate.Email(error=self.error_messages["invalid"]) self.validators.insert(0, validator) class IP(Field): """A IP address field. :param exploded: If `True`, serialize ipv6 address in long form, ie. with groups consisting entirely of zeros included. .. versionadded:: 3.8.0 """ default_error_messages = {"invalid_ip": "Not a valid IP address."} DESERIALIZATION_CLASS: type | None = None def __init__(self, *args, exploded=False, **kwargs): super().__init__(*args, **kwargs) self.exploded = exploded def _serialize(self, value, attr, obj, **kwargs) -> str | None: if value is None: return None if self.exploded: return value.exploded return value.compressed def _deserialize( self, value, attr, data, **kwargs ) -> ipaddress.IPv4Address | ipaddress.IPv6Address | None: if value is None: return None try: return (self.DESERIALIZATION_CLASS or ipaddress.ip_address)( utils.ensure_text_type(value) ) except (ValueError, TypeError) as error: raise self.make_error("invalid_ip") from error class IPv4(IP): """A IPv4 address field. .. versionadded:: 3.8.0 """ default_error_messages = {"invalid_ip": "Not a valid IPv4 address."} DESERIALIZATION_CLASS = ipaddress.IPv4Address class IPv6(IP): """A IPv6 address field. .. versionadded:: 3.8.0 """ default_error_messages = {"invalid_ip": "Not a valid IPv6 address."} DESERIALIZATION_CLASS = ipaddress.IPv6Address class IPInterface(Field): """A IPInterface field. IP interface is the non-strict form of the IPNetwork type where arbitrary host addresses are always accepted. IPAddress and mask e.g. '192.168.0.2/24' or '192.168.0.2/255.255.255.0' see https://python.readthedocs.io/en/latest/library/ipaddress.html#interface-objects :param exploded: If `True`, serialize ipv6 interface in long form, ie. with groups consisting entirely of zeros included. """ default_error_messages = {"invalid_ip_interface": "Not a valid IP interface."} DESERIALIZATION_CLASS: type | None = None def __init__(self, *args, exploded: bool = False, **kwargs): super().__init__(*args, **kwargs) self.exploded = exploded def _serialize(self, value, attr, obj, **kwargs) -> str | None: if value is None: return None if self.exploded: return value.exploded return value.compressed def _deserialize(self, value, attr, data, **kwargs) -> None | ( ipaddress.IPv4Interface | ipaddress.IPv6Interface ): if value is None: return None try: return (self.DESERIALIZATION_CLASS or ipaddress.ip_interface)( utils.ensure_text_type(value) ) except (ValueError, TypeError) as error: raise self.make_error("invalid_ip_interface") from error class IPv4Interface(IPInterface): """A IPv4 Network Interface field.""" default_error_messages = {"invalid_ip_interface": "Not a valid IPv4 interface."} DESERIALIZATION_CLASS = ipaddress.IPv4Interface class IPv6Interface(IPInterface): """A IPv6 Network Interface field.""" default_error_messages = {"invalid_ip_interface": "Not a valid IPv6 interface."} DESERIALIZATION_CLASS = ipaddress.IPv6Interface class Enum(Field): """An Enum field (de)serializing enum members by symbol (name) or by value. :param enum: Enum class :param by_value: Whether to (de)serialize by value or by name, or Field class or instance to use to (de)serialize by value. Defaults to False. If `by_value` is `False` (default), enum members are (de)serialized by symbol (name). If it is `True`, they are (de)serialized by value using :class:`Raw`. If it is a field instance or class, they are (de)serialized by value using this field. .. versionadded:: 3.18.0 """ default_error_messages = { "unknown": "Must be one of: {choices}.", } def __init__( self, enum: type[EnumType], *, by_value: bool | Field | type[Field] = False, **kwargs, ): super().__init__(**kwargs) self.enum = enum self.by_value = by_value # Serialization by name if by_value is False: self.field: Field = String() self.choices_text = ", ".join( str(self.field._serialize(m, None, None)) for m in enum.__members__ ) # Serialization by value else: if by_value is True: self.field = Raw() else: try: self.field = resolve_field_instance(by_value) except FieldInstanceResolutionError as error: raise ValueError( '"by_value" must be either a bool or a subclass or instance of ' "marshmallow.base.FieldABC." ) from error self.choices_text = ", ".join( str(self.field._serialize(m.value, None, None)) for m in enum ) def _serialize(self, value, attr, obj, **kwargs): if value is None: return None if self.by_value: val = value.value else: val = value.name return self.field._serialize(val, attr, obj, **kwargs) def _deserialize(self, value, attr, data, **kwargs): val = self.field._deserialize(value, attr, data, **kwargs) if self.by_value: try: return self.enum(val) except ValueError as error: raise self.make_error("unknown", choices=self.choices_text) from error try: return getattr(self.enum, val) except AttributeError as error: raise self.make_error("unknown", choices=self.choices_text) from error class Method(Field): """A field that takes the value returned by a `Schema ` method. :param serialize: The name of the Schema method from which to retrieve the value. The method must take an argument ``obj`` (in addition to self) that is the object to be serialized. :param deserialize: Optional name of the Schema method for deserializing a value The method must take a single argument ``value``, which is the value to deserialize. .. versionchanged:: 2.3.0 Deprecated ``method_name`` parameter in favor of ``serialize`` and allow ``serialize`` to not be passed at all. .. versionchanged:: 3.0.0 Removed ``method_name`` parameter. """ _CHECK_ATTRIBUTE = False def __init__( self, serialize: str | None = None, deserialize: str | None = None, **kwargs, ): # Set dump_only and load_only based on arguments kwargs["dump_only"] = bool(serialize) and not bool(deserialize) kwargs["load_only"] = bool(deserialize) and not bool(serialize) super().__init__(**kwargs) self.serialize_method_name = serialize self.deserialize_method_name = deserialize self._serialize_method = None self._deserialize_method = None def _bind_to_schema(self, field_name, schema): if self.serialize_method_name: self._serialize_method = utils.callable_or_raise( getattr(schema, self.serialize_method_name) ) if self.deserialize_method_name: self._deserialize_method = utils.callable_or_raise( getattr(schema, self.deserialize_method_name) ) super()._bind_to_schema(field_name, schema) def _serialize(self, value, attr, obj, **kwargs): if self._serialize_method is not None: return self._serialize_method(obj) return missing_ def _deserialize(self, value, attr, data, **kwargs): if self._deserialize_method is not None: return self._deserialize_method(value) return value class Function(Field): """A field that takes the value returned by a function. :param serialize: A callable from which to retrieve the value. The function must take a single argument ``obj`` which is the object to be serialized. It can also optionally take a ``context`` argument, which is a dictionary of context variables passed to the serializer. If no callable is provided then the ```load_only``` flag will be set to True. :param deserialize: A callable from which to retrieve the value. The function must take a single argument ``value`` which is the value to be deserialized. It can also optionally take a ``context`` argument, which is a dictionary of context variables passed to the deserializer. If no callable is provided then ```value``` will be passed through unchanged. .. versionchanged:: 2.3.0 Deprecated ``func`` parameter in favor of ``serialize``. .. versionchanged:: 3.0.0a1 Removed ``func`` parameter. """ _CHECK_ATTRIBUTE = False def __init__( self, serialize: ( typing.Callable[[typing.Any], typing.Any] | typing.Callable[[typing.Any, dict], typing.Any] | None ) = None, deserialize: ( typing.Callable[[typing.Any], typing.Any] | typing.Callable[[typing.Any, dict], typing.Any] | None ) = None, **kwargs, ): # Set dump_only and load_only based on arguments kwargs["dump_only"] = bool(serialize) and not bool(deserialize) kwargs["load_only"] = bool(deserialize) and not bool(serialize) super().__init__(**kwargs) self.serialize_func = serialize and utils.callable_or_raise(serialize) self.deserialize_func = deserialize and utils.callable_or_raise(deserialize) def _serialize(self, value, attr, obj, **kwargs): return self._call_or_raise(self.serialize_func, obj, attr) def _deserialize(self, value, attr, data, **kwargs): if self.deserialize_func: return self._call_or_raise(self.deserialize_func, value, attr) return value def _call_or_raise(self, func, value, attr): if len(utils.get_func_args(func)) > 1: if self.parent.context is None: msg = f"No context available for Function field {attr!r}" raise ValidationError(msg) return func(value, self.parent.context) return func(value) class Constant(Field): """A field that (de)serializes to a preset constant. If you only want the constant added for serialization or deserialization, you should use ``dump_only=True`` or ``load_only=True`` respectively. :param constant: The constant to return for the field attribute. """ _CHECK_ATTRIBUTE = False def __init__(self, constant: typing.Any, **kwargs): super().__init__(**kwargs) self.constant = constant self.load_default = constant self.dump_default = constant def _serialize(self, value, *args, **kwargs): return self.constant def _deserialize(self, value, *args, **kwargs): return self.constant class Inferred(Field): """A field that infers how to serialize, based on the value type. .. warning:: This class is treated as private API. Users should not need to use this class directly. """ def __init__(self): super().__init__() # We memoize the fields to avoid creating and binding new fields # every time on serialization. self._field_cache = {} def _serialize(self, value, attr, obj, **kwargs): field_cls = self.root.TYPE_MAPPING.get(type(value)) if field_cls is None: field = super() else: field = self._field_cache.get(field_cls) if field is None: field = field_cls() field._bind_to_schema(self.name, self.parent) self._field_cache[field_cls] = field return field._serialize(value, attr, obj, **kwargs) # Aliases URL = Url Str = String Bool = Boolean Int = Integer marshmallow-3.26.1/src/marshmallow/orderedset.py000066400000000000000000000055701475016050200217670ustar00rootroot00000000000000# OrderedSet # Copyright (c) 2009 Raymond Hettinger # # Permission is hereby granted, free of charge, to any person # obtaining a copy of this software and associated documentation files # (the "Software"), to deal in the Software without restriction, # including without limitation the rights to use, copy, modify, merge, # publish, distribute, sublicense, and/or sell copies of the Software, # and to permit persons to whom the Software is furnished to do so, # subject to the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR # OTHER DEALINGS IN THE SOFTWARE. from collections.abc import MutableSet class OrderedSet(MutableSet): def __init__(self, iterable=None): self.end = end = [] end += [None, end, end] # sentinel node for doubly linked list self.map = {} # key --> [key, prev, next] if iterable is not None: self |= iterable def __len__(self): return len(self.map) def __contains__(self, key): return key in self.map def add(self, key): if key not in self.map: end = self.end curr = end[1] curr[2] = end[1] = self.map[key] = [key, curr, end] def discard(self, key): if key in self.map: key, prev, next = self.map.pop(key) # noqa: A001 prev[2] = next next[1] = prev def __iter__(self): end = self.end curr = end[2] while curr is not end: yield curr[0] curr = curr[2] def __reversed__(self): end = self.end curr = end[1] while curr is not end: yield curr[0] curr = curr[1] def pop(self, last=True): if not self: raise KeyError("set is empty") key = self.end[1][0] if last else self.end[2][0] self.discard(key) return key def __repr__(self): if not self: return f"{self.__class__.__name__}()" return f"{self.__class__.__name__}({list(self)!r})" def __eq__(self, other): if isinstance(other, OrderedSet): return len(self) == len(other) and list(self) == list(other) return set(self) == set(other) if __name__ == "__main__": s = OrderedSet("abracadaba") t = OrderedSet("simsalabim") print(s | t) print(s & t) print(s - t) marshmallow-3.26.1/src/marshmallow/py.typed000066400000000000000000000000001475016050200207330ustar00rootroot00000000000000marshmallow-3.26.1/src/marshmallow/schema.py000066400000000000000000001457571475016050200211030ustar00rootroot00000000000000"""The `Schema ` class, including its metaclass and options (`class Meta `).""" from __future__ import annotations import copy import datetime as dt import decimal import functools import inspect import json import operator import typing import uuid import warnings from abc import ABCMeta from collections import OrderedDict, defaultdict from collections.abc import Mapping from itertools import zip_longest from marshmallow import base, class_registry, types from marshmallow import fields as ma_fields from marshmallow.decorators import ( POST_DUMP, POST_LOAD, PRE_DUMP, PRE_LOAD, VALIDATES, VALIDATES_SCHEMA, ) from marshmallow.error_store import ErrorStore from marshmallow.exceptions import SCHEMA, StringNotCollectionError, ValidationError from marshmallow.orderedset import OrderedSet from marshmallow.utils import ( EXCLUDE, INCLUDE, RAISE, get_value, is_collection, is_instance_or_subclass, missing, set_value, validate_unknown_parameter_value, ) from marshmallow.warnings import RemovedInMarshmallow4Warning if typing.TYPE_CHECKING: from marshmallow.fields import Field def _get_fields(attrs) -> list[tuple[str, Field]]: """Get fields from a class :param attrs: Mapping of class attributes """ return [ (field_name, field_value) for field_name, field_value in attrs.items() if is_instance_or_subclass(field_value, base.FieldABC) ] # This function allows Schemas to inherit from non-Schema classes and ensures # inheritance according to the MRO def _get_fields_by_mro(klass: SchemaMeta): """Collect fields from a class, following its method resolution order. The class itself is excluded from the search; only its parents are checked. Get fields from ``_declared_fields`` if available, else use ``__dict__``. :param klass: Class whose fields to retrieve """ mro = inspect.getmro(klass) # Combine fields from all parents # functools.reduce(operator.iadd, list_of_lists) is faster than sum(list_of_lists, []) # Loop over mro in reverse to maintain correct order of fields return functools.reduce( operator.iadd, ( _get_fields( getattr(base, "_declared_fields", base.__dict__), ) for base in mro[:0:-1] ), [], ) class SchemaMeta(ABCMeta): """Metaclass for the Schema class. Binds the declared fields to a ``_declared_fields`` attribute, which is a dictionary mapping attribute names to field objects. Also sets the ``opts`` class attribute, which is the Schema class's `class Meta ` options. """ Meta: type opts: typing.Any OPTIONS_CLASS: type _declared_fields: dict[str, Field] def __new__( mcs, # noqa: N804 name: str, bases: tuple[type, ...], attrs: dict[str, typing.Any], ) -> SchemaMeta: meta = attrs.get("Meta") ordered = getattr(meta, "ordered", False) if not ordered: # Inherit 'ordered' option # Warning: We loop through bases instead of MRO because we don't # yet have access to the class object # (i.e. can't call super before we have fields) for base_ in bases: if hasattr(base_, "Meta") and hasattr(base_.Meta, "ordered"): ordered = base_.Meta.ordered break else: ordered = False cls_fields = _get_fields(attrs) # Remove fields from list of class attributes to avoid shadowing # Schema attributes/methods in case of name conflict for field_name, _ in cls_fields: del attrs[field_name] klass = super().__new__(mcs, name, bases, attrs) inherited_fields = _get_fields_by_mro(klass) meta = klass.Meta # Set klass.opts in __new__ rather than __init__ so that it is accessible in # get_declared_fields klass.opts = klass.OPTIONS_CLASS(meta, ordered=ordered) # Add fields specified in the `include` class Meta option cls_fields += list(klass.opts.include.items()) # Assign _declared_fields on class klass._declared_fields = mcs.get_declared_fields( # noqa: SLF001 klass=klass, cls_fields=cls_fields, inherited_fields=inherited_fields, dict_cls=dict, ) return klass @classmethod def get_declared_fields( mcs, # noqa: N804 klass: SchemaMeta, cls_fields: list[tuple[str, Field]], inherited_fields: list[tuple[str, Field]], dict_cls: type[dict] = dict, ) -> dict[str, Field]: """Returns a dictionary of field_name => `Field` pairs declared on the class. This is exposed mainly so that plugins can add additional fields, e.g. fields computed from `class Meta ` options. :param klass: The class object. :param cls_fields: The fields declared on the class, including those added by the ``include`` `class Meta ` option. :param inherited_fields: Inherited fields. :param dict_cls: dict-like class to use for dict output Default to ``dict``. """ return dict_cls(inherited_fields + cls_fields) def __init__(cls, name, bases, attrs): super().__init__(name, bases, attrs) if name and cls.opts.register: class_registry.register(name, cls) cls._hooks = cls.resolve_hooks() def resolve_hooks(cls) -> dict[str, list[tuple[str, bool, dict]]]: """Add in the decorated processors By doing this after constructing the class, we let standard inheritance do all the hard work. """ mro = inspect.getmro(cls) hooks: dict[str, list[tuple[str, bool, dict]]] = defaultdict(list) for attr_name in dir(cls): # Need to look up the actual descriptor, not whatever might be # bound to the class. This needs to come from the __dict__ of the # declaring class. for parent in mro: try: attr = parent.__dict__[attr_name] except KeyError: continue else: break else: # In case we didn't find the attribute and didn't break above. # We should never hit this - it's just here for completeness # to exclude the possibility of attr being undefined. continue try: hook_config: dict[str, list[tuple[bool, dict]]] = ( attr.__marshmallow_hook__ ) except AttributeError: pass else: for tag, config in hook_config.items(): # Use name here so we can get the bound method later, in # case the processor was a descriptor or something. hooks[tag].extend( (attr_name, many, kwargs) for many, kwargs in config ) return hooks class SchemaOpts: """Defines defaults for `marshmallow.Schema.Meta`.""" def __init__(self, meta: type, ordered: bool = False): # noqa: FBT001, FBT002 self.fields = getattr(meta, "fields", ()) if not isinstance(self.fields, (list, tuple)): raise ValueError("`fields` option must be a list or tuple.") self.additional = getattr(meta, "additional", ()) if not isinstance(self.additional, (list, tuple)): raise ValueError("`additional` option must be a list or tuple.") if self.fields and self.additional: raise ValueError( "Cannot set both `fields` and `additional` options for the same Schema." ) self.exclude = getattr(meta, "exclude", ()) if not isinstance(self.exclude, (list, tuple)): raise ValueError("`exclude` must be a list or tuple.") self.dateformat = getattr(meta, "dateformat", None) self.datetimeformat = getattr(meta, "datetimeformat", None) self.timeformat = getattr(meta, "timeformat", None) if hasattr(meta, "json_module"): warnings.warn( "The json_module class Meta option is deprecated. Use render_module instead.", RemovedInMarshmallow4Warning, stacklevel=2, ) render_module = getattr(meta, "json_module", json) else: render_module = json self.render_module = getattr(meta, "render_module", render_module) if hasattr(meta, "ordered"): warnings.warn( "The `ordered` `class Meta` option is deprecated. " "Field order is already preserved by default. " "Set `Schema.dict_class` to OrderedDict to maintain the previous behavior.", RemovedInMarshmallow4Warning, stacklevel=2, ) self.ordered = getattr(meta, "ordered", ordered) self.index_errors = getattr(meta, "index_errors", True) self.include = getattr(meta, "include", {}) self.load_only = getattr(meta, "load_only", ()) self.dump_only = getattr(meta, "dump_only", ()) self.unknown = validate_unknown_parameter_value(getattr(meta, "unknown", RAISE)) self.register = getattr(meta, "register", True) self.many = getattr(meta, "many", False) class Schema(base.SchemaABC, metaclass=SchemaMeta): """Base schema class with which to define schemas. Example usage: .. code-block:: python import datetime as dt from dataclasses import dataclass from marshmallow import Schema, fields @dataclass class Album: title: str release_date: dt.date class AlbumSchema(Schema): title = fields.Str() release_date = fields.Date() album = Album("Beggars Banquet", dt.date(1968, 12, 6)) schema = AlbumSchema() data = schema.dump(album) data # {'release_date': '1968-12-06', 'title': 'Beggars Banquet'} :param only: Whitelist of the declared fields to select when instantiating the Schema. If None, all fields are used. Nested fields can be represented with dot delimiters. :param exclude: Blacklist of the declared fields to exclude when instantiating the Schema. If a field appears in both `only` and `exclude`, it is not used. Nested fields can be represented with dot delimiters. :param many: Should be set to `True` if ``obj`` is a collection so that the object will be serialized to a list. :param context: Optional context passed to :class:`fields.Method` and :class:`fields.Function` fields. :param load_only: Fields to skip during serialization (write-only fields) :param dump_only: Fields to skip during deserialization (read-only fields) :param partial: Whether to ignore missing fields and not require any fields declared. Propagates down to ``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. :param unknown: Whether to exclude, include, or raise an error for unknown fields in the data. Use `EXCLUDE`, `INCLUDE` or `RAISE`. .. versionchanged:: 3.0.0 `prefix` parameter removed. """ TYPE_MAPPING: dict[type, type[Field]] = { str: ma_fields.String, bytes: ma_fields.String, dt.datetime: ma_fields.DateTime, float: ma_fields.Float, bool: ma_fields.Boolean, tuple: ma_fields.Raw, list: ma_fields.Raw, set: ma_fields.Raw, int: ma_fields.Integer, uuid.UUID: ma_fields.UUID, dt.time: ma_fields.Time, dt.date: ma_fields.Date, dt.timedelta: ma_fields.TimeDelta, decimal.Decimal: ma_fields.Decimal, } #: Overrides for default schema-level error messages error_messages: dict[str, str] = {} _default_error_messages: dict[str, str] = { "type": "Invalid input type.", "unknown": "Unknown field.", } OPTIONS_CLASS: type = SchemaOpts set_class = OrderedSet # These get set by SchemaMeta opts: typing.Any _declared_fields: dict[str, Field] = {} _hooks: dict[str, list[tuple[str, bool, dict]]] = {} class Meta: """Options object for a Schema. Example usage: :: from marshmallow import Schema class MySchema(Schema): class Meta: fields = ("id", "email", "date_created") exclude = ("password", "secret_attribute") .. admonition:: A note on type checking Type checkers will only check the attributes of the `Meta ` class if you explicitly subclass `marshmallow.Schema.Meta`. .. code-block:: python from marshmallow import Schema class MySchema(Schema): # Not checked by type checkers class Meta: additional = True class MySchema2(Schema): # Type checkers will check attributes class Meta(Schema.Opts): additional = True # Incompatible types in assignment .. versionremoved:: 3.0.0b7 Remove ``strict``. .. versionadded:: 3.0.0b12 Add `unknown`. .. versionchanged:: 3.0.0b17 Rename ``dateformat`` to `datetimeformat`. .. versionadded:: 3.9.0 Add `timeformat`. .. versionchanged:: 3.26.0 Deprecate `ordered`. Field order is preserved by default. """ fields: typing.ClassVar[tuple[str, ...] | list[str]] """Fields to include in the (de)serialized result""" additional: typing.ClassVar[tuple[str, ...] | list[str]] """Fields to include in addition to the explicitly declared fields. `additional ` and `fields ` are mutually-exclusive options. """ include: typing.ClassVar[dict[str, Field]] """Dictionary of additional fields to include in the schema. It is usually better to define fields as class variables, but you may need to use this option, e.g., if your fields are Python keywords. """ exclude: typing.ClassVar[tuple[str, ...] | list[str]] """Fields to exclude in the serialized result. Nested fields can be represented with dot delimiters. """ many: typing.ClassVar[bool] """Whether data should be (de)serialized as a collection by default.""" dateformat: typing.ClassVar[str] """Default format for `Date ` fields.""" datetimeformat: typing.ClassVar[str] """Default format for `DateTime ` fields.""" timeformat: typing.ClassVar[str] """Default format for `Time ` fields.""" # FIXME: Use a more constrained type here. # ClassVar[RenderModule] doesn't work. render_module: typing.Any """ Module to use for `loads ` and `dumps `. Defaults to `json` from the standard library. """ ordered: typing.ClassVar[bool] """If `True`, `Schema.dump ` is a `collections.OrderedDict`.""" index_errors: typing.ClassVar[bool] """If `True`, errors dictionaries will include the index of invalid items in a collection.""" load_only: typing.ClassVar[tuple[str, ...] | list[str]] """Fields to exclude from serialized results""" dump_only: typing.ClassVar[tuple[str, ...] | list[str]] """Fields to exclude from serialized results""" unknown: typing.ClassVar[str] """Whether to exclude, include, or raise an error for unknown fields in the data. Use `EXCLUDE`, `INCLUDE` or `RAISE`. """ register: typing.ClassVar[bool] """Whether to register the `Schema ` with marshmallow's internal class registry. Must be `True` if you intend to refer to this `Schema ` by class name in `Nested` fields. Only set this to `False` when memory usage is critical. Defaults to `True`. """ def __init__( self, *, only: types.StrSequenceOrSet | None = None, exclude: types.StrSequenceOrSet = (), many: bool | None = None, context: dict | None = None, load_only: types.StrSequenceOrSet = (), dump_only: types.StrSequenceOrSet = (), partial: bool | types.StrSequenceOrSet | None = None, unknown: str | None = None, ): # Raise error if only or exclude is passed as string, not list of strings if only is not None and not is_collection(only): raise StringNotCollectionError('"only" should be a list of strings') if not is_collection(exclude): raise StringNotCollectionError('"exclude" should be a list of strings') # copy declared fields from metaclass self.declared_fields = copy.deepcopy(self._declared_fields) self.many = self.opts.many if many is None else many self.only = only self.exclude: set[typing.Any] | typing.MutableSet[typing.Any] = set( self.opts.exclude ) | set(exclude) self.ordered = self.opts.ordered self.load_only = set(load_only) or set(self.opts.load_only) self.dump_only = set(dump_only) or set(self.opts.dump_only) self.partial = partial self.unknown = ( self.opts.unknown if unknown is None else validate_unknown_parameter_value(unknown) ) if context: warnings.warn( "The `context` parameter is deprecated and will be removed in marshmallow 4.0. " "Use `contextvars.ContextVar` to pass context instead.", RemovedInMarshmallow4Warning, stacklevel=2, ) self.context = context or {} self._normalize_nested_options() #: Dictionary mapping field_names -> :class:`Field` objects self.fields: dict[str, Field] = {} self.load_fields: dict[str, Field] = {} self.dump_fields: dict[str, Field] = {} self._init_fields() messages = {} messages.update(self._default_error_messages) for cls in reversed(self.__class__.__mro__): messages.update(getattr(cls, "error_messages", {})) messages.update(self.error_messages or {}) self.error_messages = messages def __repr__(self) -> str: return f"<{self.__class__.__name__}(many={self.many})>" @property def dict_class(self) -> type[dict]: """`dict` type to return when serializing.""" if self.ordered: return OrderedDict return dict @classmethod def from_dict( cls, fields: dict[str, Field], *, name: str = "GeneratedSchema", ) -> type[Schema]: """Generate a `Schema ` class given a dictionary of fields. .. code-block:: python from marshmallow import Schema, fields PersonSchema = Schema.from_dict({"name": fields.Str()}) print(PersonSchema().load({"name": "David"})) # => {'name': 'David'} Generated schemas are not added to the class registry and therefore cannot be referred to by name in `Nested` fields. :param fields: Dictionary mapping field names to field instances. :param name: Optional name for the class, which will appear in the ``repr`` for the class. .. versionadded:: 3.0.0 """ Meta = type( "GeneratedMeta", (getattr(cls, "Meta", object),), {"register": False} ) return type(name, (cls,), {**fields.copy(), "Meta": Meta}) ##### Override-able methods ##### def handle_error( self, error: ValidationError, data: typing.Any, *, many: bool, **kwargs ): """Custom error handler function for the schema. :param error: The `ValidationError` raised during (de)serialization. :param data: The original input data. :param many: Value of ``many`` on dump or load. :param partial: Value of ``partial`` on load. .. versionchanged:: 3.0.0rc9 Receives `many` and `partial` (on deserialization) as keyword arguments. """ def get_attribute(self, obj: typing.Any, attr: str, default: typing.Any): """Defines how to pull values from an object to serialize. .. versionchanged:: 3.0.0a1 Changed position of ``obj`` and ``attr``. """ return get_value(obj, attr, default) ##### Serialization/Deserialization API ##### @staticmethod def _call_and_store(getter_func, data, *, field_name, error_store, index=None): """Call ``getter_func`` with ``data`` as its argument, and store any `ValidationErrors`. :param getter_func: Function for getting the serialized/deserialized value from ``data``. :param data: The data passed to ``getter_func``. :param field_name: Field name. :param index: Index of the item being validated, if validating a collection, otherwise `None`. """ try: value = getter_func(data) except ValidationError as error: error_store.store_error(error.messages, field_name, index=index) # When a Nested field fails validation, the marshalled data is stored # on the ValidationError's valid_data attribute return error.valid_data or missing return value def _serialize(self, obj: typing.Any, *, many: bool = False): """Serialize ``obj``. :param obj: The object(s) to serialize. :param many: `True` if ``data`` should be serialized as a collection. :return: A dictionary of the serialized data """ if many and obj is not None: return [self._serialize(d, many=False) for d in obj] ret = self.dict_class() for attr_name, field_obj in self.dump_fields.items(): value = field_obj.serialize(attr_name, obj, accessor=self.get_attribute) if value is missing: continue key = field_obj.data_key if field_obj.data_key is not None else attr_name ret[key] = value return ret def dump(self, obj: typing.Any, *, many: bool | None = None): """Serialize an object to native Python data types according to this Schema's fields. :param obj: The object to serialize. :param many: Whether to serialize `obj` as a collection. If `None`, the value for `self.many` is used. :return: Serialized data .. versionadded:: 1.0.0 .. versionchanged:: 3.0.0b7 This method returns the serialized data rather than a ``(data, errors)`` duple. A :exc:`ValidationError ` is raised if ``obj`` is invalid. .. versionchanged:: 3.0.0rc9 Validation no longer occurs upon serialization. """ many = self.many if many is None else bool(many) if self._hooks[PRE_DUMP]: processed_obj = self._invoke_dump_processors( PRE_DUMP, obj, many=many, original_data=obj ) else: processed_obj = obj result = self._serialize(processed_obj, many=many) if self._hooks[POST_DUMP]: result = self._invoke_dump_processors( POST_DUMP, result, many=many, original_data=obj ) return result def dumps(self, obj: typing.Any, *args, many: bool | None = None, **kwargs): """Same as :meth:`dump`, except return a JSON-encoded string. :param obj: The object to serialize. :param many: Whether to serialize `obj` as a collection. If `None`, the value for `self.many` is used. :return: A ``json`` string .. versionadded:: 1.0.0 .. versionchanged:: 3.0.0b7 This method returns the serialized data rather than a ``(data, errors)`` duple. A :exc:`ValidationError ` is raised if ``obj`` is invalid. """ serialized = self.dump(obj, many=many) return self.opts.render_module.dumps(serialized, *args, **kwargs) def _deserialize( self, data: ( typing.Mapping[str, typing.Any] | typing.Iterable[typing.Mapping[str, typing.Any]] ), *, error_store: ErrorStore, many: bool = False, partial=None, unknown=RAISE, index=None, ) -> typing.Any | list[typing.Any]: """Deserialize ``data``. :param data: The data to deserialize. :param error_store: Structure to store errors. :param many: `True` if ``data`` should be deserialized as a collection. :param partial: Whether to ignore missing fields and not require any fields declared. Propagates down to ``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. :param unknown: Whether to exclude, include, or raise an error for unknown fields in the data. Use `EXCLUDE`, `INCLUDE` or `RAISE`. :param index: Index of the item being serialized (for storing errors) if serializing a collection, otherwise `None`. :return: The deserialized data as `dict_class` instance or list of `dict_class` instances if `many` is `True`. """ index_errors = self.opts.index_errors index = index if index_errors else None if many: if not is_collection(data): error_store.store_error([self.error_messages["type"]], index=index) ret_l = [] else: ret_l = [ self._deserialize( typing.cast(dict, d), error_store=error_store, many=False, partial=partial, unknown=unknown, index=idx, ) for idx, d in enumerate(data) ] return ret_l ret_d = self.dict_class() # Check data is a dict if not isinstance(data, Mapping): error_store.store_error([self.error_messages["type"]], index=index) else: partial_is_collection = is_collection(partial) for attr_name, field_obj in self.load_fields.items(): field_name = ( field_obj.data_key if field_obj.data_key is not None else attr_name ) raw_value = data.get(field_name, missing) if raw_value is missing: # Ignore missing field if we're allowed to. if partial is True or ( partial_is_collection and attr_name in partial ): continue d_kwargs = {} # Allow partial loading of nested schemas. if partial_is_collection: prefix = field_name + "." len_prefix = len(prefix) sub_partial = [ f[len_prefix:] for f in partial if f.startswith(prefix) ] d_kwargs["partial"] = sub_partial elif partial is not None: d_kwargs["partial"] = partial def getter( val, field_obj=field_obj, field_name=field_name, d_kwargs=d_kwargs ): return field_obj.deserialize( val, field_name, data, **d_kwargs, ) value = self._call_and_store( getter_func=getter, data=raw_value, field_name=field_name, error_store=error_store, index=index, ) if value is not missing: key = field_obj.attribute or attr_name set_value(ret_d, key, value) if unknown != EXCLUDE: fields = { field_obj.data_key if field_obj.data_key is not None else field_name for field_name, field_obj in self.load_fields.items() } for key in set(data) - fields: value = data[key] if unknown == INCLUDE: ret_d[key] = value elif unknown == RAISE: error_store.store_error( [self.error_messages["unknown"]], key, (index if index_errors else None), ) return ret_d def load( self, data: ( typing.Mapping[str, typing.Any] | typing.Iterable[typing.Mapping[str, typing.Any]] ), *, many: bool | None = None, partial: bool | types.StrSequenceOrSet | None = None, unknown: str | None = None, ): """Deserialize a data structure to an object defined by this Schema's fields. :param data: The data to deserialize. :param many: Whether to deserialize `data` as a collection. If `None`, the value for `self.many` is used. :param partial: Whether to ignore missing fields and not require any fields declared. Propagates down to ``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. :param unknown: Whether to exclude, include, or raise an error for unknown fields in the data. Use `EXCLUDE`, `INCLUDE` or `RAISE`. If `None`, the value for `self.unknown` is used. :return: Deserialized data .. versionadded:: 1.0.0 .. versionchanged:: 3.0.0b7 This method returns the deserialized data rather than a ``(data, errors)`` duple. A :exc:`ValidationError ` is raised if invalid data are passed. """ return self._do_load( data, many=many, partial=partial, unknown=unknown, postprocess=True ) def loads( self, json_data: str | bytes | bytearray, *, many: bool | None = None, partial: bool | types.StrSequenceOrSet | None = None, unknown: str | None = None, **kwargs, ): """Same as :meth:`load`, except it uses `marshmallow.Schema.Meta.render_module` to deserialize the passed string before passing data to :meth:`load`. :param json_data: A string of the data to deserialize. :param many: Whether to deserialize `obj` as a collection. If `None`, the value for `self.many` is used. :param partial: Whether to ignore missing fields and not require any fields declared. Propagates down to ``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. :param unknown: Whether to exclude, include, or raise an error for unknown fields in the data. Use `EXCLUDE`, `INCLUDE` or `RAISE`. If `None`, the value for `self.unknown` is used. :return: Deserialized data .. versionadded:: 1.0.0 .. versionchanged:: 3.0.0b7 This method returns the deserialized data rather than a ``(data, errors)`` duple. A :exc:`ValidationError ` is raised if invalid data are passed. """ data = self.opts.render_module.loads(json_data, **kwargs) return self.load(data, many=many, partial=partial, unknown=unknown) def _run_validator( self, validator_func: types.SchemaValidator, output, *, original_data, error_store: ErrorStore, many: bool, partial: bool | types.StrSequenceOrSet | None, pass_original: bool, index: int | None = None, ): try: if pass_original: # Pass original, raw data (before unmarshalling) validator_func(output, original_data, partial=partial, many=many) else: validator_func(output, partial=partial, many=many) except ValidationError as err: field_name = err.field_name data_key: str if field_name == SCHEMA: data_key = SCHEMA else: field_obj: Field | None = None try: field_obj = self.fields[field_name] except KeyError: if field_name in self.declared_fields: field_obj = self.declared_fields[field_name] if field_obj: data_key = ( field_obj.data_key if field_obj.data_key is not None else field_name ) else: data_key = field_name error_store.store_error(err.messages, data_key, index=index) def validate( self, data: ( typing.Mapping[str, typing.Any] | typing.Iterable[typing.Mapping[str, typing.Any]] ), *, many: bool | None = None, partial: bool | types.StrSequenceOrSet | None = None, ) -> dict[str, list[str]]: """Validate `data` against the schema, returning a dictionary of validation errors. :param data: The data to validate. :param many: Whether to validate `data` as a collection. If `None`, the value for `self.many` is used. :param partial: Whether to ignore missing fields and not require any fields declared. Propagates down to ``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. :return: A dictionary of validation errors. .. versionadded:: 1.1.0 """ try: self._do_load(data, many=many, partial=partial, postprocess=False) except ValidationError as exc: return typing.cast(dict[str, list[str]], exc.messages) return {} ##### Private Helpers ##### def _do_load( self, data: ( typing.Mapping[str, typing.Any] | typing.Iterable[typing.Mapping[str, typing.Any]] ), *, many: bool | None = None, partial: bool | types.StrSequenceOrSet | None = None, unknown: str | None = None, postprocess: bool = True, ): """Deserialize `data`, returning the deserialized result. This method is private API. :param data: The data to deserialize. :param many: Whether to deserialize `data` as a collection. If `None`, the value for `self.many` is used. :param partial: Whether to validate required fields. If its value is an iterable, only fields listed in that iterable will be ignored will be allowed missing. If `True`, all fields will be allowed missing. If `None`, the value for `self.partial` is used. :param unknown: Whether to exclude, include, or raise an error for unknown fields in the data. Use `EXCLUDE`, `INCLUDE` or `RAISE`. If `None`, the value for `self.unknown` is used. :param postprocess: Whether to run post_load methods.. :return: Deserialized data """ error_store = ErrorStore() errors: dict[str, list[str]] = {} many = self.many if many is None else bool(many) unknown = ( self.unknown if unknown is None else validate_unknown_parameter_value(unknown) ) if partial is None: partial = self.partial # Run preprocessors if self._hooks[PRE_LOAD]: try: processed_data = self._invoke_load_processors( PRE_LOAD, data, many=many, original_data=data, partial=partial ) except ValidationError as err: errors = err.normalized_messages() result: list | dict | None = None else: processed_data = data if not errors: # Deserialize data result = self._deserialize( processed_data, error_store=error_store, many=many, partial=partial, unknown=unknown, ) # Run field-level validation self._invoke_field_validators( error_store=error_store, data=result, many=many ) # Run schema-level validation if self._hooks[VALIDATES_SCHEMA]: field_errors = bool(error_store.errors) self._invoke_schema_validators( error_store=error_store, pass_many=True, data=result, original_data=data, many=many, partial=partial, field_errors=field_errors, ) self._invoke_schema_validators( error_store=error_store, pass_many=False, data=result, original_data=data, many=many, partial=partial, field_errors=field_errors, ) errors = error_store.errors # Run post processors if not errors and postprocess and self._hooks[POST_LOAD]: try: result = self._invoke_load_processors( POST_LOAD, result, many=many, original_data=data, partial=partial, ) except ValidationError as err: errors = err.normalized_messages() if errors: exc = ValidationError(errors, data=data, valid_data=result) self.handle_error(exc, data, many=many, partial=partial) raise exc return result def _normalize_nested_options(self) -> None: """Apply then flatten nested schema options. This method is private API. """ if self.only is not None: # Apply the only option to nested fields. self.__apply_nested_option("only", self.only, "intersection") # Remove the child field names from the only option. self.only = self.set_class([field.split(".", 1)[0] for field in self.only]) if self.exclude: # Apply the exclude option to nested fields. self.__apply_nested_option("exclude", self.exclude, "union") # Remove the parent field names from the exclude option. self.exclude = self.set_class( [field for field in self.exclude if "." not in field] ) def __apply_nested_option(self, option_name, field_names, set_operation) -> None: """Apply nested options to nested fields""" # Split nested field names on the first dot. nested_fields = [name.split(".", 1) for name in field_names if "." in name] # Partition the nested field names by parent field. nested_options = defaultdict(list) # type: defaultdict for parent, nested_names in nested_fields: nested_options[parent].append(nested_names) # Apply the nested field options. for key, options in iter(nested_options.items()): new_options = self.set_class(options) original_options = getattr(self.declared_fields[key], option_name, ()) if original_options: if set_operation == "union": new_options |= self.set_class(original_options) if set_operation == "intersection": new_options &= self.set_class(original_options) setattr(self.declared_fields[key], option_name, new_options) def _init_fields(self) -> None: """Update self.fields, self.load_fields, and self.dump_fields based on schema options. This method is private API. """ if self.opts.fields: available_field_names = self.set_class(self.opts.fields) else: available_field_names = self.set_class(self.declared_fields.keys()) if self.opts.additional: available_field_names |= self.set_class(self.opts.additional) invalid_fields = self.set_class() if self.only is not None: # Return only fields specified in only option field_names: typing.AbstractSet[typing.Any] = self.set_class(self.only) invalid_fields |= field_names - available_field_names else: field_names = available_field_names # If "exclude" option or param is specified, remove those fields. if self.exclude: # Note that this isn't available_field_names, since we want to # apply "only" for the actual calculation. field_names = field_names - self.exclude invalid_fields |= self.exclude - available_field_names if invalid_fields: message = f"Invalid fields for {self}: {invalid_fields}." raise ValueError(message) fields_dict = self.dict_class() for field_name in field_names: field_obj = self.declared_fields.get(field_name, ma_fields.Inferred()) self._bind_field(field_name, field_obj) fields_dict[field_name] = field_obj load_fields, dump_fields = self.dict_class(), self.dict_class() for field_name, field_obj in fields_dict.items(): if not field_obj.dump_only: load_fields[field_name] = field_obj if not field_obj.load_only: dump_fields[field_name] = field_obj dump_data_keys = [ field_obj.data_key if field_obj.data_key is not None else name for name, field_obj in dump_fields.items() ] if len(dump_data_keys) != len(set(dump_data_keys)): data_keys_duplicates = { x for x in dump_data_keys if dump_data_keys.count(x) > 1 } raise ValueError( "The data_key argument for one or more fields collides " "with another field's name or data_key argument. " "Check the following field names and " f"data_key arguments: {list(data_keys_duplicates)}" ) load_attributes = [obj.attribute or name for name, obj in load_fields.items()] if len(load_attributes) != len(set(load_attributes)): attributes_duplicates = { x for x in load_attributes if load_attributes.count(x) > 1 } raise ValueError( "The attribute argument for one or more fields collides " "with another field's name or attribute argument. " "Check the following field names and " f"attribute arguments: {list(attributes_duplicates)}" ) self.fields = fields_dict self.dump_fields = dump_fields self.load_fields = load_fields def on_bind_field(self, field_name: str, field_obj: Field) -> None: """Hook to modify a field when it is bound to the `Schema `. No-op by default. """ return def _bind_field(self, field_name: str, field_obj: Field) -> None: """Bind field to the schema, setting any necessary attributes on the field (e.g. parent and name). Also set field load_only and dump_only values if field_name was specified in `class Meta `. """ if field_name in self.load_only: field_obj.load_only = True if field_name in self.dump_only: field_obj.dump_only = True try: field_obj._bind_to_schema(field_name, self) # noqa: SLF001 except TypeError as error: # Field declared as a class, not an instance. Ignore type checking because # we handle unsupported arg types, i.e. this is dead code from # the type checker's perspective. if isinstance(field_obj, type) and issubclass(field_obj, base.FieldABC): msg = ( f'Field for "{field_name}" must be declared as a ' "Field instance, not a class. " f'Did you mean "fields.{field_obj.__name__}()"?' # type: ignore[attr-defined] ) raise TypeError(msg) from error raise self.on_bind_field(field_name, field_obj) def _invoke_dump_processors( self, tag: str, data, *, many: bool, original_data=None ): # The pass_many post-dump processors may do things like add an envelope, so # invoke those after invoking the non-pass_many processors which will expect # to get a list of items. data = self._invoke_processors( tag, pass_many=False, data=data, many=many, original_data=original_data ) return self._invoke_processors( tag, pass_many=True, data=data, many=many, original_data=original_data ) def _invoke_load_processors( self, tag: str, data, *, many: bool, original_data, partial: bool | types.StrSequenceOrSet | None, ): # This has to invert the order of the dump processors, so run the pass_many # processors first. data = self._invoke_processors( tag, pass_many=True, data=data, many=many, original_data=original_data, partial=partial, ) return self._invoke_processors( tag, pass_many=False, data=data, many=many, original_data=original_data, partial=partial, ) def _invoke_field_validators(self, *, error_store: ErrorStore, data, many: bool): for attr_name, _, validator_kwargs in self._hooks[VALIDATES]: validator = getattr(self, attr_name) field_name = validator_kwargs["field_name"] try: field_obj = self.fields[field_name] except KeyError as error: if field_name in self.declared_fields: continue raise ValueError(f'"{field_name}" field does not exist.') from error data_key = ( field_obj.data_key if field_obj.data_key is not None else field_name ) if many: for idx, item in enumerate(data): try: value = item[field_obj.attribute or field_name] except KeyError: pass else: validated_value = self._call_and_store( getter_func=validator, data=value, field_name=data_key, error_store=error_store, index=(idx if self.opts.index_errors else None), ) if validated_value is missing: item.pop(field_name, None) else: try: value = data[field_obj.attribute or field_name] except KeyError: pass else: validated_value = self._call_and_store( getter_func=validator, data=value, field_name=data_key, error_store=error_store, ) if validated_value is missing: data.pop(field_name, None) def _invoke_schema_validators( self, *, error_store: ErrorStore, pass_many: bool, data, original_data, many: bool, partial: bool | types.StrSequenceOrSet | None, field_errors: bool = False, ): for attr_name, hook_many, validator_kwargs in self._hooks[VALIDATES_SCHEMA]: if hook_many != pass_many: continue validator = getattr(self, attr_name) if field_errors and validator_kwargs["skip_on_field_errors"]: continue pass_original = validator_kwargs.get("pass_original", False) if many and not pass_many: for idx, (item, orig) in enumerate(zip(data, original_data)): self._run_validator( validator, item, original_data=orig, error_store=error_store, many=many, partial=partial, index=idx, pass_original=pass_original, ) else: self._run_validator( validator, data, original_data=original_data, error_store=error_store, many=many, pass_original=pass_original, partial=partial, ) def _invoke_processors( self, tag: str, *, pass_many: bool, data, many: bool, original_data=None, **kwargs, ): for attr_name, hook_many, processor_kwargs in self._hooks[tag]: if hook_many != pass_many: continue # This will be a bound method. processor = getattr(self, attr_name) pass_original = processor_kwargs.get("pass_original", False) if many and not pass_many: if pass_original: data = [ processor(item, original, many=many, **kwargs) for item, original in zip_longest(data, original_data) ] else: data = [processor(item, many=many, **kwargs) for item in data] elif pass_original: data = processor(data, original_data, many=many, **kwargs) else: data = processor(data, many=many, **kwargs) return data BaseSchema = Schema # for backwards compatibility marshmallow-3.26.1/src/marshmallow/types.py000066400000000000000000000017231475016050200207670ustar00rootroot00000000000000"""Type aliases. .. warning:: This module is provisional. Types may be modified, added, and removed between minor releases. """ from __future__ import annotations import typing #: A type that can be either a sequence of strings or a set of strings StrSequenceOrSet = typing.Union[typing.Sequence[str], typing.AbstractSet[str]] #: Type for validator functions Validator = typing.Callable[[typing.Any], typing.Any] class SchemaValidator(typing.Protocol): def __call__( self, output: typing.Any, original_data: typing.Any = ..., *, partial: bool | StrSequenceOrSet | None = None, many: bool = False, ) -> None: ... class RenderModule(typing.Protocol): def dumps( self, obj: typing.Any, *args: typing.Any, **kwargs: typing.Any ) -> str: ... def loads( self, json_data: str | bytes | bytearray, *args: typing.Any, **kwargs: typing.Any, ) -> typing.Any: ... marshmallow-3.26.1/src/marshmallow/utils.py000066400000000000000000000272471475016050200207740ustar00rootroot00000000000000"""Utility methods for marshmallow.""" # ruff: noqa: T201, T203 from __future__ import annotations import collections import datetime as dt import functools import inspect import json import re import typing import warnings from collections.abc import Mapping from email.utils import format_datetime, parsedate_to_datetime from pprint import pprint as py_pprint from marshmallow.base import FieldABC from marshmallow.exceptions import FieldInstanceResolutionError from marshmallow.warnings import RemovedInMarshmallow4Warning if typing.TYPE_CHECKING: from marshmallow.fields import Field EXCLUDE = "exclude" INCLUDE = "include" RAISE = "raise" _UNKNOWN_VALUES = {EXCLUDE, INCLUDE, RAISE} class _Missing: def __bool__(self): return False def __copy__(self): return self def __deepcopy__(self, _): return self def __repr__(self): return "" # Singleton value that indicates that a field's value is missing from input # dict passed to `Schema.load `. If the field's value is not required, # it's ``default`` value is used. missing = _Missing() def is_generator(obj) -> bool: """Return True if ``obj`` is a generator""" return inspect.isgeneratorfunction(obj) or inspect.isgenerator(obj) def is_iterable_but_not_string(obj) -> bool: """Return True if ``obj`` is an iterable object that isn't a string.""" return (hasattr(obj, "__iter__") and not hasattr(obj, "strip")) or is_generator(obj) def is_collection(obj) -> bool: """Return True if ``obj`` is a collection type, e.g list, tuple, queryset.""" return is_iterable_but_not_string(obj) and not isinstance(obj, Mapping) def is_instance_or_subclass(val, class_) -> bool: """Return True if ``val`` is either a subclass or instance of ``class_``.""" try: return issubclass(val, class_) except TypeError: return isinstance(val, class_) def is_keyed_tuple(obj) -> bool: """Return True if ``obj`` has keyed tuple behavior, such as namedtuples or SQLAlchemy's KeyedTuples. """ return isinstance(obj, tuple) and hasattr(obj, "_fields") def pprint(obj, *args, **kwargs) -> None: """Pretty-printing function that can pretty-print OrderedDicts like regular dictionaries. Useful for printing the output of :meth:`marshmallow.Schema.dump`. .. deprecated:: 3.7.0 marshmallow.pprint will be removed in marshmallow 4. """ warnings.warn( "marshmallow's pprint function is deprecated and will be removed in marshmallow 4.", RemovedInMarshmallow4Warning, stacklevel=2, ) if isinstance(obj, collections.OrderedDict): print(json.dumps(obj, *args, **kwargs)) else: py_pprint(obj, *args, **kwargs) # https://stackoverflow.com/a/27596917 def is_aware(datetime: dt.datetime) -> bool: return ( datetime.tzinfo is not None and datetime.tzinfo.utcoffset(datetime) is not None ) def from_rfc(datestring: str) -> dt.datetime: """Parse a RFC822-formatted datetime string and return a datetime object. https://stackoverflow.com/questions/885015/how-to-parse-a-rfc-2822-date-time-into-a-python-datetime # noqa: B950 """ return parsedate_to_datetime(datestring) def rfcformat(datetime: dt.datetime) -> str: """Return the RFC822-formatted representation of a datetime object. :param datetime: The datetime. """ return format_datetime(datetime) # Hat tip to Django for ISO8601 deserialization functions _iso8601_datetime_re = re.compile( r"(?P\d{4})-(?P\d{1,2})-(?P\d{1,2})" r"[T ](?P\d{1,2}):(?P\d{1,2})" r"(?::(?P\d{1,2})(?:\.(?P\d{1,6})\d{0,6})?)?" r"(?PZ|[+-]\d{2}(?::?\d{2})?)?$" ) _iso8601_date_re = re.compile(r"(?P\d{4})-(?P\d{1,2})-(?P\d{1,2})$") _iso8601_time_re = re.compile( r"(?P\d{1,2}):(?P\d{1,2})" r"(?::(?P\d{1,2})(?:\.(?P\d{1,6})\d{0,6})?)?" ) def get_fixed_timezone(offset: float | dt.timedelta) -> dt.timezone: """Return a tzinfo instance with a fixed offset from UTC.""" if isinstance(offset, dt.timedelta): offset = offset.total_seconds() // 60 sign = "-" if offset < 0 else "+" hhmm = "{:02d}{:02d}".format(*divmod(abs(offset), 60)) name = sign + hhmm return dt.timezone(dt.timedelta(minutes=offset), name) def from_iso_datetime(value): """Parse a string and return a datetime.datetime. This function supports time zone offsets. When the input contains one, the output uses a timezone with a fixed offset from UTC. """ match = _iso8601_datetime_re.match(value) if not match: raise ValueError("Not a valid ISO8601-formatted datetime string") kw = match.groupdict() kw["microsecond"] = kw["microsecond"] and kw["microsecond"].ljust(6, "0") tzinfo = kw.pop("tzinfo") if tzinfo == "Z": tzinfo = dt.timezone.utc elif tzinfo is not None: offset_mins = int(tzinfo[-2:]) if len(tzinfo) > 3 else 0 offset = 60 * int(tzinfo[1:3]) + offset_mins if tzinfo[0] == "-": offset = -offset tzinfo = get_fixed_timezone(offset) kw = {k: int(v) for k, v in kw.items() if v is not None} kw["tzinfo"] = tzinfo return dt.datetime(**kw) # noqa: DTZ001 def from_iso_time(value): """Parse a string and return a datetime.time. This function doesn't support time zone offsets. """ match = _iso8601_time_re.match(value) if not match: raise ValueError("Not a valid ISO8601-formatted time string") kw = match.groupdict() kw["microsecond"] = kw["microsecond"] and kw["microsecond"].ljust(6, "0") kw = {k: int(v) for k, v in kw.items() if v is not None} return dt.time(**kw) def from_iso_date(value): """Parse a string and return a datetime.date.""" match = _iso8601_date_re.match(value) if not match: raise ValueError("Not a valid ISO8601-formatted date string") kw = {k: int(v) for k, v in match.groupdict().items()} return dt.date(**kw) def from_timestamp(value: typing.Any) -> dt.datetime: if value is True or value is False: raise ValueError("Not a valid POSIX timestamp") value = float(value) if value < 0: raise ValueError("Not a valid POSIX timestamp") # Load a timestamp with utc as timezone to prevent using system timezone. # Then set timezone to None, to let the Field handle adding timezone info. try: return dt.datetime.fromtimestamp(value, tz=dt.timezone.utc).replace(tzinfo=None) except OverflowError as exc: raise ValueError("Timestamp is too large") from exc except OSError as exc: raise ValueError("Error converting value to datetime") from exc def from_timestamp_ms(value: typing.Any) -> dt.datetime: value = float(value) return from_timestamp(value / 1000) def timestamp( value: dt.datetime, ) -> float: if not is_aware(value): # When a date is naive, use UTC as zone info to prevent using system timezone. value = value.replace(tzinfo=dt.timezone.utc) return value.timestamp() def timestamp_ms(value: dt.datetime) -> float: return timestamp(value) * 1000 def isoformat(datetime: dt.datetime) -> str: """Return the ISO8601-formatted representation of a datetime object. :param datetime: The datetime. """ return datetime.isoformat() def to_iso_time(time: dt.time) -> str: return dt.time.isoformat(time) def to_iso_date(date: dt.date) -> str: return dt.date.isoformat(date) def ensure_text_type(val: str | bytes) -> str: if isinstance(val, bytes): val = val.decode("utf-8") return str(val) def pluck(dictlist: list[dict[str, typing.Any]], key: str): """Extracts a list of dictionary values from a list of dictionaries. :: >>> dlist = [{'id': 1, 'name': 'foo'}, {'id': 2, 'name': 'bar'}] >>> pluck(dlist, 'id') [1, 2] """ return [d[key] for d in dictlist] # Various utilities for pulling keyed values from objects def get_value(obj, key: int | str, default=missing): """Helper for pulling a keyed value off various types of objects. Fields use this method by default to access attributes of the source object. For object `x` and attribute `i`, this method first tries to access `x[i]`, and then falls back to `x.i` if an exception is raised. .. warning:: If an object `x` does not raise an exception when `x[i]` does not exist, `get_value` will never check the value `x.i`. Consider overriding `marshmallow.fields.Field.get_value` in this case. """ if not isinstance(key, int) and "." in key: return _get_value_for_keys(obj, key.split("."), default) return _get_value_for_key(obj, key, default) def _get_value_for_keys(obj, keys, default): if len(keys) == 1: return _get_value_for_key(obj, keys[0], default) return _get_value_for_keys( _get_value_for_key(obj, keys[0], default), keys[1:], default ) def _get_value_for_key(obj, key, default): if not hasattr(obj, "__getitem__"): return getattr(obj, key, default) try: return obj[key] except (KeyError, IndexError, TypeError, AttributeError): return getattr(obj, key, default) def set_value(dct: dict[str, typing.Any], key: str, value: typing.Any): """Set a value in a dict. If `key` contains a '.', it is assumed be a path (i.e. dot-delimited string) to the value's location. :: >>> d = {} >>> set_value(d, 'foo.bar', 42) >>> d {'foo': {'bar': 42}} """ if "." in key: head, rest = key.split(".", 1) target = dct.setdefault(head, {}) if not isinstance(target, dict): raise ValueError( f"Cannot set {key} in {head} due to existing value: {target}" ) set_value(target, rest, value) else: dct[key] = value def callable_or_raise(obj): """Check that an object is callable, else raise a :exc:`TypeError`.""" if not callable(obj): raise TypeError(f"Object {obj!r} is not callable.") return obj def _signature(func: typing.Callable) -> list[str]: return list(inspect.signature(func).parameters.keys()) def get_func_args(func: typing.Callable) -> list[str]: """Given a callable, return a list of argument names. Handles `functools.partial` objects and class-based callables. .. versionchanged:: 3.0.0a1 Do not return bound arguments, eg. ``self``. """ if inspect.isfunction(func) or inspect.ismethod(func): return _signature(func) if isinstance(func, functools.partial): return _signature(func.func) # Callable class return _signature(func) def resolve_field_instance(cls_or_instance: type[Field] | Field) -> Field: """Return a field instance from a field class or instance. :param cls_or_instance: Field class or instance. """ if isinstance(cls_or_instance, type): if not issubclass(cls_or_instance, FieldABC): raise FieldInstanceResolutionError return cls_or_instance() if not isinstance(cls_or_instance, FieldABC): raise FieldInstanceResolutionError return cls_or_instance def timedelta_to_microseconds(value: dt.timedelta) -> int: """Compute the total microseconds of a timedelta https://github.com/python/cpython/blob/bb3e0c240bc60fe08d332ff5955d54197f79751c/Lib/datetime.py#L665-L667 # noqa: B950 """ return (value.days * (24 * 3600) + value.seconds) * 1000000 + value.microseconds def validate_unknown_parameter_value(obj: typing.Any) -> str: if obj not in _UNKNOWN_VALUES: raise ValueError( f"Object {obj!r} is not a valid value for the 'unknown' parameter" ) return obj marshmallow-3.26.1/src/marshmallow/validate.py000066400000000000000000000601141475016050200214130ustar00rootroot00000000000000"""Validation classes for various types of data.""" from __future__ import annotations import re import typing import warnings from abc import ABC, abstractmethod from itertools import zip_longest from operator import attrgetter from marshmallow.exceptions import ValidationError from marshmallow.warnings import ChangedInMarshmallow4Warning if typing.TYPE_CHECKING: from marshmallow import types _T = typing.TypeVar("_T") class Validator(ABC): """Abstract base class for validators. .. note:: This class does not provide any validation behavior. It is only used to add a useful `__repr__` implementation for validators. """ error: str | None = None def __repr__(self) -> str: args = self._repr_args() args = f"{args}, " if args else "" return f"<{self.__class__.__name__}({args}error={self.error!r})>" def _repr_args(self) -> str: """A string representation of the args passed to this validator. Used by `__repr__`. """ return "" @abstractmethod def __call__(self, value: typing.Any) -> typing.Any: ... class And(Validator): """Compose multiple validators and combine their error messages. Example: :: from marshmallow import validate, ValidationError def is_even(value): if value % 2 != 0: raise ValidationError("Not an even value.") validator = validate.And(validate.Range(min=0), is_even) validator(-1) # ValidationError: ['Must be greater than or equal to 0.', 'Not an even value.'] :param validators: Validators to combine. :param error: Error message to use when a validator returns ``False``. """ default_error_message = "Invalid value." def __init__(self, *validators: types.Validator, error: str | None = None): self.validators = tuple(validators) self.error: str = error or self.default_error_message def _repr_args(self) -> str: return f"validators={self.validators!r}" def __call__(self, value: typing.Any) -> typing.Any: errors: list[str | dict] = [] kwargs: dict[str, typing.Any] = {} for validator in self.validators: try: r = validator(value) if not isinstance(validator, Validator) and r is False: warnings.warn( "Returning `False` from a validator is deprecated. " "Raise a `ValidationError` instead.", ChangedInMarshmallow4Warning, stacklevel=2, ) raise ValidationError(self.error) # noqa: TRY301 except ValidationError as err: kwargs.update(err.kwargs) if isinstance(err.messages, dict): errors.append(err.messages) else: errors.extend(err.messages) if errors: raise ValidationError(errors, **kwargs) return value class URL(Validator): """Validate a URL. :param relative: Whether to allow relative URLs. :param absolute: Whether to allow absolute URLs. :param error: Error message to raise in case of a validation error. Can be interpolated with `{input}`. :param schemes: Valid schemes. By default, ``http``, ``https``, ``ftp``, and ``ftps`` are allowed. :param require_tld: Whether to reject non-FQDN hostnames. """ class RegexMemoizer: def __init__(self): self._memoized = {} def _regex_generator( self, *, relative: bool, absolute: bool, require_tld: bool ) -> typing.Pattern: hostname_variants = [ # a normal domain name, expressed in [A-Z0-9] chars with hyphens allowed only in the middle # note that the regex will be compiled with IGNORECASE, so these are upper and lowercase chars ( r"(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+" r"(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)" ), # or the special string 'localhost' r"localhost", # or IPv4 r"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}", # or IPv6 r"\[[A-F0-9]*:[A-F0-9:]+\]", ] if not require_tld: # allow dotless hostnames hostname_variants.append(r"(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.?)") absolute_part = "".join( ( # scheme (e.g. 'https://', 'ftp://', etc) # this is validated separately against allowed schemes, so in the regex # we simply want to capture its existence r"(?:[a-z0-9\.\-\+]*)://", # userinfo, for URLs encoding authentication # e.g. 'ftp://foo:bar@ftp.example.org/' r"(?:(?:[a-z0-9\-._~!$&'()*+,;=:]|%[0-9a-f]{2})*@)?", # netloc, the hostname/domain part of the URL plus the optional port r"(?:", "|".join(hostname_variants), r")", r"(?::\d+)?", ) ) relative_part = r"(?:/?|[/?]\S+)\Z" if relative: if absolute: parts: tuple[str, ...] = ( r"^(", absolute_part, r")?", relative_part, ) else: parts = (r"^", relative_part) else: parts = (r"^", absolute_part, relative_part) return re.compile("".join(parts), re.IGNORECASE) def __call__( self, *, relative: bool, absolute: bool, require_tld: bool ) -> typing.Pattern: key = (relative, absolute, require_tld) if key not in self._memoized: self._memoized[key] = self._regex_generator( relative=relative, absolute=absolute, require_tld=require_tld ) return self._memoized[key] _regex = RegexMemoizer() default_message = "Not a valid URL." default_schemes = {"http", "https", "ftp", "ftps"} def __init__( self, *, relative: bool = False, absolute: bool = True, schemes: types.StrSequenceOrSet | None = None, require_tld: bool = True, error: str | None = None, ): if not relative and not absolute: raise ValueError( "URL validation cannot set both relative and absolute to False." ) self.relative = relative self.absolute = absolute self.error: str = error or self.default_message self.schemes = schemes or self.default_schemes self.require_tld = require_tld def _repr_args(self) -> str: return f"relative={self.relative!r}, absolute={self.absolute!r}" def _format_error(self, value) -> str: return self.error.format(input=value) def __call__(self, value: str) -> str: message = self._format_error(value) if not value: raise ValidationError(message) # Check first if the scheme is valid scheme = None if "://" in value: scheme = value.split("://")[0].lower() if scheme not in self.schemes: raise ValidationError(message) regex = self._regex( relative=self.relative, absolute=self.absolute, require_tld=self.require_tld ) # Hostname is optional for file URLS. If absent it means `localhost`. # Fill it in for the validation if needed if scheme == "file" and value.startswith("file:///"): matched = regex.search(value.replace("file:///", "file://localhost/", 1)) else: matched = regex.search(value) if not matched: raise ValidationError(message) return value class Email(Validator): """Validate an email address. :param error: Error message to raise in case of a validation error. Can be interpolated with `{input}`. """ USER_REGEX = re.compile( r"(^[-!#$%&'*+/=?^`{}|~\w]+(\.[-!#$%&'*+/=?^`{}|~\w]+)*\Z" # dot-atom # quoted-string r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]' r'|\\[\001-\011\013\014\016-\177])*"\Z)', re.IGNORECASE | re.UNICODE, ) DOMAIN_REGEX = re.compile( # domain r"(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+" r"(?:[A-Z]{2,6}|[A-Z0-9-]{2,})\Z" # literal form, ipv4 address (SMTP 4.1.3) r"|^\[(25[0-5]|2[0-4]\d|[0-1]?\d?\d)" r"(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}\]\Z", re.IGNORECASE | re.UNICODE, ) DOMAIN_WHITELIST = ("localhost",) default_message = "Not a valid email address." def __init__(self, *, error: str | None = None): self.error: str = error or self.default_message def _format_error(self, value: str) -> str: return self.error.format(input=value) def __call__(self, value: str) -> str: message = self._format_error(value) if not value or "@" not in value: raise ValidationError(message) user_part, domain_part = value.rsplit("@", 1) if not self.USER_REGEX.match(user_part): raise ValidationError(message) if domain_part not in self.DOMAIN_WHITELIST: if not self.DOMAIN_REGEX.match(domain_part): try: domain_part = domain_part.encode("idna").decode("ascii") except UnicodeError: pass else: if self.DOMAIN_REGEX.match(domain_part): return value raise ValidationError(message) return value class Range(Validator): """Validator which succeeds if the value passed to it is within the specified range. If ``min`` is not specified, or is specified as `None`, no lower bound exists. If ``max`` is not specified, or is specified as `None`, no upper bound exists. The inclusivity of the bounds (if they exist) is configurable. If ``min_inclusive`` is not specified, or is specified as `True`, then the ``min`` bound is included in the range. If ``max_inclusive`` is not specified, or is specified as `True`, then the ``max`` bound is included in the range. :param min: The minimum value (lower bound). If not provided, minimum value will not be checked. :param max: The maximum value (upper bound). If not provided, maximum value will not be checked. :param min_inclusive: Whether the `min` bound is included in the range. :param max_inclusive: Whether the `max` bound is included in the range. :param error: Error message to raise in case of a validation error. Can be interpolated with `{input}`, `{min}` and `{max}`. """ message_min = "Must be {min_op} {{min}}." message_max = "Must be {max_op} {{max}}." message_all = "Must be {min_op} {{min}} and {max_op} {{max}}." message_gte = "greater than or equal to" message_gt = "greater than" message_lte = "less than or equal to" message_lt = "less than" def __init__( self, min=None, # noqa: A002 max=None, # noqa: A002 *, min_inclusive: bool = True, max_inclusive: bool = True, error: str | None = None, ): self.min = min self.max = max self.error = error self.min_inclusive = min_inclusive self.max_inclusive = max_inclusive # interpolate messages based on bound inclusivity self.message_min = self.message_min.format( min_op=self.message_gte if self.min_inclusive else self.message_gt ) self.message_max = self.message_max.format( max_op=self.message_lte if self.max_inclusive else self.message_lt ) self.message_all = self.message_all.format( min_op=self.message_gte if self.min_inclusive else self.message_gt, max_op=self.message_lte if self.max_inclusive else self.message_lt, ) def _repr_args(self) -> str: return f"min={self.min!r}, max={self.max!r}, min_inclusive={self.min_inclusive!r}, max_inclusive={self.max_inclusive!r}" def _format_error(self, value: _T, message: str) -> str: return (self.error or message).format(input=value, min=self.min, max=self.max) def __call__(self, value: _T) -> _T: if self.min is not None and ( value < self.min if self.min_inclusive else value <= self.min ): message = self.message_min if self.max is None else self.message_all raise ValidationError(self._format_error(value, message)) if self.max is not None and ( value > self.max if self.max_inclusive else value >= self.max ): message = self.message_max if self.min is None else self.message_all raise ValidationError(self._format_error(value, message)) return value _SizedT = typing.TypeVar("_SizedT", bound=typing.Sized) class Length(Validator): """Validator which succeeds if the value passed to it has a length between a minimum and maximum. Uses len(), so it can work for strings, lists, or anything with length. :param min: The minimum length. If not provided, minimum length will not be checked. :param max: The maximum length. If not provided, maximum length will not be checked. :param equal: The exact length. If provided, maximum and minimum length will not be checked. :param error: Error message to raise in case of a validation error. Can be interpolated with `{input}`, `{min}` and `{max}`. """ message_min = "Shorter than minimum length {min}." message_max = "Longer than maximum length {max}." message_all = "Length must be between {min} and {max}." message_equal = "Length must be {equal}." def __init__( self, min: int | None = None, # noqa: A002 max: int | None = None, # noqa: A002 *, equal: int | None = None, error: str | None = None, ): if equal is not None and any([min, max]): raise ValueError( "The `equal` parameter was provided, maximum or " "minimum parameter must not be provided." ) self.min = min self.max = max self.error = error self.equal = equal def _repr_args(self) -> str: return f"min={self.min!r}, max={self.max!r}, equal={self.equal!r}" def _format_error(self, value: _SizedT, message: str) -> str: return (self.error or message).format( input=value, min=self.min, max=self.max, equal=self.equal ) def __call__(self, value: _SizedT) -> _SizedT: length = len(value) if self.equal is not None: if length != self.equal: raise ValidationError(self._format_error(value, self.message_equal)) return value if self.min is not None and length < self.min: message = self.message_min if self.max is None else self.message_all raise ValidationError(self._format_error(value, message)) if self.max is not None and length > self.max: message = self.message_max if self.min is None else self.message_all raise ValidationError(self._format_error(value, message)) return value class Equal(Validator): """Validator which succeeds if the ``value`` passed to it is equal to ``comparable``. :param comparable: The object to compare to. :param error: Error message to raise in case of a validation error. Can be interpolated with `{input}` and `{other}`. """ default_message = "Must be equal to {other}." def __init__(self, comparable, *, error: str | None = None): self.comparable = comparable self.error: str = error or self.default_message def _repr_args(self) -> str: return f"comparable={self.comparable!r}" def _format_error(self, value: _T) -> str: return self.error.format(input=value, other=self.comparable) def __call__(self, value: _T) -> _T: if value != self.comparable: raise ValidationError(self._format_error(value)) return value class Regexp(Validator): """Validator which succeeds if the ``value`` matches ``regex``. .. note:: Uses `re.match`, which searches for a match at the beginning of a string. :param regex: The regular expression string to use. Can also be a compiled regular expression pattern. :param flags: The regexp flags to use, for example re.IGNORECASE. Ignored if ``regex`` is not a string. :param error: Error message to raise in case of a validation error. Can be interpolated with `{input}` and `{regex}`. """ default_message = "String does not match expected pattern." def __init__( self, regex: str | bytes | typing.Pattern, flags: int = 0, *, error: str | None = None, ): self.regex = ( re.compile(regex, flags) if isinstance(regex, (str, bytes)) else regex ) self.error: str = error or self.default_message def _repr_args(self) -> str: return f"regex={self.regex!r}" def _format_error(self, value: str | bytes) -> str: return self.error.format(input=value, regex=self.regex.pattern) @typing.overload def __call__(self, value: str) -> str: ... @typing.overload def __call__(self, value: bytes) -> bytes: ... def __call__(self, value): if self.regex.match(value) is None: raise ValidationError(self._format_error(value)) return value class Predicate(Validator): """Call the specified ``method`` of the ``value`` object. The validator succeeds if the invoked method returns an object that evaluates to True in a Boolean context. Any additional keyword argument will be passed to the method. :param method: The name of the method to invoke. :param error: Error message to raise in case of a validation error. Can be interpolated with `{input}` and `{method}`. :param kwargs: Additional keyword arguments to pass to the method. """ default_message = "Invalid input." def __init__(self, method: str, *, error: str | None = None, **kwargs): self.method = method self.error: str = error or self.default_message self.kwargs = kwargs def _repr_args(self) -> str: return f"method={self.method!r}, kwargs={self.kwargs!r}" def _format_error(self, value: typing.Any) -> str: return self.error.format(input=value, method=self.method) def __call__(self, value: _T) -> _T: method = getattr(value, self.method) if not method(**self.kwargs): raise ValidationError(self._format_error(value)) return value class NoneOf(Validator): """Validator which fails if ``value`` is a member of ``iterable``. :param iterable: A sequence of invalid values. :param error: Error message to raise in case of a validation error. Can be interpolated using `{input}` and `{values}`. """ default_message = "Invalid input." def __init__(self, iterable: typing.Iterable, *, error: str | None = None): self.iterable = iterable self.values_text = ", ".join(str(each) for each in self.iterable) self.error: str = error or self.default_message def _repr_args(self) -> str: return f"iterable={self.iterable!r}" def _format_error(self, value) -> str: return self.error.format(input=value, values=self.values_text) def __call__(self, value: typing.Any) -> typing.Any: try: if value in self.iterable: raise ValidationError(self._format_error(value)) except TypeError: pass return value class OneOf(Validator): """Validator which succeeds if ``value`` is a member of ``choices``. :param choices: A sequence of valid values. :param labels: Optional sequence of labels to pair with the choices. :param error: Error message to raise in case of a validation error. Can be interpolated with `{input}`, `{choices}` and `{labels}`. """ default_message = "Must be one of: {choices}." def __init__( self, choices: typing.Iterable, labels: typing.Iterable[str] | None = None, *, error: str | None = None, ): self.choices = choices self.choices_text = ", ".join(str(choice) for choice in self.choices) self.labels = labels if labels is not None else [] self.labels_text = ", ".join(str(label) for label in self.labels) self.error: str = error or self.default_message def _repr_args(self) -> str: return f"choices={self.choices!r}, labels={self.labels!r}" def _format_error(self, value) -> str: return self.error.format( input=value, choices=self.choices_text, labels=self.labels_text ) def __call__(self, value: typing.Any) -> typing.Any: try: if value not in self.choices: raise ValidationError(self._format_error(value)) except TypeError as error: raise ValidationError(self._format_error(value)) from error return value def options( self, valuegetter: str | typing.Callable[[typing.Any], typing.Any] = str, ) -> typing.Iterable[tuple[typing.Any, str]]: """Return a generator over the (value, label) pairs, where value is a string associated with each choice. This convenience method is useful to populate, for instance, a form select field. :param valuegetter: Can be a callable or a string. In the former case, it must be a one-argument callable which returns the value of a choice. In the latter case, the string specifies the name of an attribute of the choice objects. Defaults to `str()` or `str()`. """ valuegetter = valuegetter if callable(valuegetter) else attrgetter(valuegetter) pairs = zip_longest(self.choices, self.labels, fillvalue="") return ((valuegetter(choice), label) for choice, label in pairs) class ContainsOnly(OneOf): """Validator which succeeds if ``value`` is a sequence and each element in the sequence is also in the sequence passed as ``choices``. Empty input is considered valid. :param choices: Same as :class:`OneOf`. :param labels: Same as :class:`OneOf`. :param error: Same as :class:`OneOf`. .. versionchanged:: 3.0.0b2 Duplicate values are considered valid. .. versionchanged:: 3.0.0b2 Empty input is considered valid. Use `validate.Length(min=1) ` to validate against empty inputs. """ default_message = "One or more of the choices you made was not in: {choices}." def _format_error(self, value) -> str: value_text = ", ".join(str(val) for val in value) return super()._format_error(value_text) def __call__(self, value: typing.Sequence[_T]) -> typing.Sequence[_T]: # We can't use set.issubset because does not handle unhashable types for val in value: if val not in self.choices: raise ValidationError(self._format_error(value)) return value class ContainsNoneOf(NoneOf): """Validator which fails if ``value`` is a sequence and any element in the sequence is a member of the sequence passed as ``iterable``. Empty input is considered valid. :param iterable: Same as :class:`NoneOf`. :param error: Same as :class:`NoneOf`. .. versionadded:: 3.6.0 """ default_message = "One or more of the choices you made was in: {values}." def _format_error(self, value) -> str: value_text = ", ".join(str(val) for val in value) return super()._format_error(value_text) def __call__(self, value: typing.Sequence[_T]) -> typing.Sequence[_T]: for val in value: if val in self.iterable: raise ValidationError(self._format_error(value)) return value marshmallow-3.26.1/src/marshmallow/warnings.py000066400000000000000000000003001475016050200214410ustar00rootroot00000000000000class Marshmallow4Warning(DeprecationWarning): pass class ChangedInMarshmallow4Warning(Marshmallow4Warning): pass class RemovedInMarshmallow4Warning(Marshmallow4Warning): pass marshmallow-3.26.1/tests/000077500000000000000000000000001475016050200152735ustar00rootroot00000000000000marshmallow-3.26.1/tests/__init__.py000066400000000000000000000000001475016050200173720ustar00rootroot00000000000000marshmallow-3.26.1/tests/base.py000066400000000000000000000213641475016050200165650ustar00rootroot00000000000000"""Test utilities and fixtures.""" import datetime as dt import functools import uuid from enum import Enum, IntEnum from zoneinfo import ZoneInfo import simplejson from marshmallow import Schema, fields, missing, post_load, validate from marshmallow.exceptions import ValidationError central = ZoneInfo("America/Chicago") class GenderEnum(IntEnum): male = 1 female = 2 non_binary = 3 class HairColorEnum(Enum): black = "black hair" brown = "brown hair" blond = "blond hair" red = "red hair" class DateEnum(Enum): date_1 = dt.date(2004, 2, 29) date_2 = dt.date(2008, 2, 29) date_3 = dt.date(2012, 2, 29) ALL_FIELDS = [ fields.String, fields.Integer, fields.Boolean, fields.Float, fields.DateTime, fields.Time, fields.Date, fields.TimeDelta, fields.Dict, fields.Url, fields.Email, fields.UUID, fields.Decimal, fields.IP, fields.IPv4, fields.IPv6, fields.IPInterface, fields.IPv4Interface, fields.IPv6Interface, functools.partial(fields.Enum, GenderEnum), functools.partial(fields.Enum, HairColorEnum, by_value=fields.String), functools.partial(fields.Enum, GenderEnum, by_value=fields.Integer), ] ##### Custom asserts ##### def assert_date_equal(d1, d2): assert d1.year == d2.year assert d1.month == d2.month assert d1.day == d2.day def assert_time_equal(t1, t2): assert t1.hour == t2.hour assert t1.minute == t2.minute assert t1.second == t2.second assert t1.microsecond == t2.microsecond ##### Models ##### class User: SPECIES = "Homo sapiens" def __init__( self, name, *, age=0, id_=None, homepage=None, email=None, registered=True, time_registered=None, birthdate=None, birthtime=None, balance=100, sex=GenderEnum.male, hair_color=HairColorEnum.black, employer=None, various_data=None, ): self.name = name self.age = age # A naive datetime self.created = dt.datetime(2013, 11, 10, 14, 20, 58) # A TZ-aware datetime self.updated = dt.datetime(2013, 11, 10, 14, 20, 58, tzinfo=central) self.id = id_ self.homepage = homepage self.email = email self.balance = balance self.registered = registered self.hair_colors = list(HairColorEnum.__members__) self.sex_choices = list(GenderEnum.__members__) self.finger_count = 10 self.uid = uuid.uuid1() self.time_registered = time_registered or dt.time(1, 23, 45, 6789) self.birthdate = birthdate or dt.date(2013, 1, 23) self.birthtime = birthtime or dt.time(0, 1, 2, 3333) self.activation_date = dt.date(2013, 12, 11) self.sex = sex self.hair_color = hair_color self.employer = employer self.relatives = [] self.various_data = various_data or { "pets": ["cat", "dog"], "address": "1600 Pennsylvania Ave\nWashington, DC 20006", } @property def since_created(self): return dt.datetime(2013, 11, 24) - self.created def __repr__(self): return f"" class Blog: def __init__(self, title, user, collaborators=None, categories=None, id_=None): self.title = title self.user = user self.collaborators = collaborators or [] # List/tuple of users self.categories = categories self.id = id_ def __contains__(self, item): return item.name in [each.name for each in self.collaborators] class DummyModel: def __init__(self, foo): self.foo = foo def __eq__(self, other): return self.foo == other.foo def __str__(self): return f"bar {self.foo}" ###### Schemas ##### class Uppercased(fields.Field): """Custom field formatting example.""" def _serialize(self, value, attr, obj): if value: return value.upper() return None def get_lowername(obj): if obj is None: return missing if isinstance(obj, dict): return obj.get("name").lower() return obj.name.lower() class UserSchema(Schema): name = fields.String() age: fields.Field = fields.Float() created = fields.DateTime() created_formatted = fields.DateTime( format="%Y-%m-%d", attribute="created", dump_only=True ) created_iso = fields.DateTime(format="iso", attribute="created", dump_only=True) updated = fields.DateTime() species = fields.String(attribute="SPECIES") id = fields.String(dump_default="no-id") uppername = Uppercased(attribute="name", dump_only=True) homepage = fields.Url() email = fields.Email() balance = fields.Decimal() is_old: fields.Field = fields.Method("get_is_old") lowername = fields.Function(get_lowername) registered = fields.Boolean() hair_colors = fields.List(fields.Raw) sex_choices = fields.List(fields.Raw) finger_count = fields.Integer() uid = fields.UUID() time_registered = fields.Time() birthdate = fields.Date() birthtime = fields.Time() activation_date = fields.Date() since_created = fields.TimeDelta() sex = fields.Str(validate=validate.OneOf(list(GenderEnum.__members__))) various_data = fields.Dict() class Meta: render_module = simplejson def get_is_old(self, obj): if obj is None: return missing if isinstance(obj, dict): age = obj.get("age") else: age = obj.age try: return age > 80 except TypeError as te: raise ValidationError(str(te)) from te @post_load def make_user(self, data, **kwargs): return User(**data) class UserMetaSchema(Schema): """The equivalent of the UserSchema, using the ``fields`` option.""" uppername = Uppercased(attribute="name", dump_only=True) balance = fields.Decimal() is_old = fields.Method("get_is_old") lowername = fields.Function(get_lowername) species = fields.String(attribute="SPECIES") homepage = fields.Url() email = fields.Email() various_data = fields.Dict() def get_is_old(self, obj): if obj is None: return missing if isinstance(obj, dict): age = obj.get("age") else: age = obj.age try: return age > 80 except TypeError as te: raise ValidationError(str(te)) from te class Meta: fields = ( "name", "age", "created", "updated", "id", "homepage", "uppername", "email", "balance", "is_old", "lowername", "species", "registered", "hair_colors", "sex_choices", "finger_count", "uid", "time_registered", "birthdate", "birthtime", "since_created", "various_data", ) class UserExcludeSchema(UserSchema): class Meta: exclude = ("created", "updated") class UserAdditionalSchema(Schema): lowername = fields.Function(lambda obj: obj.name.lower()) class Meta: additional = ("name", "age", "created", "email") class UserIntSchema(UserSchema): age = fields.Integer() class UserFloatStringSchema(UserSchema): age = fields.Float(as_string=True) class ExtendedUserSchema(UserSchema): is_old = fields.Boolean() class UserRelativeUrlSchema(UserSchema): homepage = fields.Url(relative=True) class BlogSchema(Schema): title = fields.String() user = fields.Nested(UserSchema) collaborators = fields.List(fields.Nested(UserSchema())) categories = fields.List(fields.String) id = fields.String() class BlogUserMetaSchema(Schema): user = fields.Nested(UserMetaSchema()) collaborators = fields.List(fields.Nested(UserMetaSchema())) class BlogSchemaMeta(Schema): """Same as BlogSerializer but using ``fields`` options.""" user = fields.Nested(UserSchema) collaborators = fields.List(fields.Nested(UserSchema())) class Meta: fields = ("title", "user", "collaborators", "categories", "id") class BlogOnlySchema(Schema): title = fields.String() user = fields.Nested(UserSchema) collaborators = fields.List(fields.Nested(UserSchema(only=("id",)))) class BlogSchemaExclude(BlogSchema): user = fields.Nested(UserSchema, exclude=("uppername", "species")) class BlogSchemaOnlyExclude(BlogSchema): user = fields.Nested(UserSchema, only=("name",), exclude=("name", "species")) class mockjson: # noqa: N801 @staticmethod def dumps(val): return b"{'foo': 42}" @staticmethod def loads(val): return {"foo": 42} marshmallow-3.26.1/tests/conftest.py000066400000000000000000000010661475016050200174750ustar00rootroot00000000000000"""Pytest fixtures that are available in all test modules.""" import pytest from tests.base import Blog, User, UserSchema @pytest.fixture def user(): return User(name="Monty", age=42.3, homepage="http://monty.python.org/") @pytest.fixture def blog(user): col1 = User(name="Mick", age=123) col2 = User(name="Keith", age=456) return Blog( "Monty's blog", user=user, categories=["humor", "violence"], collaborators=[col1, col2], ) @pytest.fixture def serialized_user(user): return UserSchema().dump(user) marshmallow-3.26.1/tests/foo_serializer.py000066400000000000000000000001411475016050200206550ustar00rootroot00000000000000from marshmallow import Schema, fields class FooSerializer(Schema): _id = fields.Integer() marshmallow-3.26.1/tests/mypy_test_cases/000077500000000000000000000000001475016050200205065ustar00rootroot00000000000000marshmallow-3.26.1/tests/mypy_test_cases/test_class_registry.py000066400000000000000000000001531475016050200251530ustar00rootroot00000000000000from marshmallow import class_registry # Works without passing `all` class_registry.get_class("MySchema") marshmallow-3.26.1/tests/mypy_test_cases/test_schema.py000066400000000000000000000013541475016050200233620ustar00rootroot00000000000000import json from marshmallow import EXCLUDE, Schema from marshmallow.fields import Integer, String # Test that valid `Meta` class attributes pass type checking class MySchema(Schema): foo = String() bar = Integer() class Meta(Schema.Meta): fields = ("foo", "bar") additional = ("baz", "qux") include = { "foo2": String(), } exclude = ("bar", "baz") many = True dateformat = "%Y-%m-%d" datetimeformat = "%Y-%m-%dT%H:%M:%S" timeformat = "%H:%M:%S" render_module = json ordered = False index_errors = True load_only = ("foo", "bar") dump_only = ("baz", "qux") unknown = EXCLUDE register = False marshmallow-3.26.1/tests/mypy_test_cases/test_validation_error.py000066400000000000000000000011031475016050200254550ustar00rootroot00000000000000from __future__ import annotations import marshmallow as ma # OK types for 'message' ma.ValidationError("foo") ma.ValidationError(["foo"]) ma.ValidationError({"foo": "bar"}) # non-OK types for 'message' ma.ValidationError(0) # type: ignore[arg-type] # 'messages' is a dict|list err = ma.ValidationError("foo") a: dict | list = err.messages # union type can't assign to non-union type b: str = err.messages # type: ignore[assignment] c: dict = err.messages # type: ignore[assignment] # 'messages_dict' is a dict, so that it can assign to a dict d: dict = err.messages_dict marshmallow-3.26.1/tests/test_decorators.py000066400000000000000000001000341475016050200210470ustar00rootroot00000000000000import pytest from marshmallow import ( EXCLUDE, INCLUDE, RAISE, Schema, ValidationError, fields, post_dump, post_load, pre_dump, pre_load, validates, validates_schema, ) @pytest.mark.parametrize("partial_val", (True, False)) def test_decorated_processors(partial_val): class ExampleSchema(Schema): """Includes different ways to invoke decorators and set up methods""" TAG = "TAG" value = fields.Integer(as_string=True) # Implicit default raw, pre dump, static method. @pre_dump def increment_value(self, item, **kwargs): assert "many" in kwargs item["value"] += 1 return item # Implicit default raw, post dump, class method. @post_dump def add_tag(self, item, **kwargs): assert "many" in kwargs item["value"] = self.TAG + item["value"] return item # Explicitly raw, post dump, instance method. @post_dump(pass_many=True) def add_envelope(self, data, many, **kwargs): key = self.get_envelope_key(many) return {key: data} # Explicitly raw, pre load, instance method. @pre_load(pass_many=True) def remove_envelope(self, data, many, partial, **kwargs): assert partial is partial_val key = self.get_envelope_key(many) return data[key] @staticmethod def get_envelope_key(many): return "data" if many else "datum" # Explicitly not raw, pre load, instance method. @pre_load(pass_many=False) def remove_tag(self, item, partial, **kwargs): assert partial is partial_val assert "many" in kwargs item["value"] = item["value"][len(self.TAG) :] return item # Explicit default raw, post load, instance method. @post_load() def decrement_value(self, item, partial, **kwargs): assert partial is partial_val assert "many" in kwargs item["value"] -= 1 return item schema = ExampleSchema(partial=partial_val) # Need to re-create these because the processors will modify in place. def make_item(): return {"value": 3} def make_items(): return [make_item(), {"value": 5}] item_dumped = schema.dump(make_item()) assert item_dumped == {"datum": {"value": "TAG4"}} item_loaded = schema.load(item_dumped) assert item_loaded == make_item() items_dumped = schema.dump(make_items(), many=True) assert items_dumped == {"data": [{"value": "TAG4"}, {"value": "TAG6"}]} items_loaded = schema.load(items_dumped, many=True) assert items_loaded == make_items() # Regression test for https://github.com/marshmallow-code/marshmallow/issues/347 @pytest.mark.parametrize("unknown", (EXCLUDE, INCLUDE, RAISE)) def test_decorated_processor_returning_none(unknown): class PostSchema(Schema): value = fields.Integer() @post_load def load_none(self, item, **kwargs): return None @post_dump def dump_none(self, item, **kwargs): return None class PreSchema(Schema): value = fields.Integer() @pre_load def load_none(self, item, **kwargs): return None @pre_dump def dump_none(self, item, **kwargs): return None schema = PostSchema(unknown=unknown) assert schema.dump({"value": 3}) is None assert schema.load({"value": 3}) is None schema = PreSchema(unknown=unknown) assert schema.dump({"value": 3}) == {} with pytest.raises(ValidationError) as excinfo: schema.load({"value": 3}) assert excinfo.value.messages == {"_schema": ["Invalid input type."]} class TestPassOriginal: def test_pass_original_single(self): class MySchema(Schema): foo = fields.Raw() @post_load(pass_original=True) def post_load(self, data, original_data, **kwargs): ret = data.copy() ret["_post_load"] = original_data["sentinel"] return ret @post_dump(pass_original=True) def post_dump(self, data, obj, **kwargs): ret = data.copy() ret["_post_dump"] = obj["sentinel"] return ret schema = MySchema(unknown=EXCLUDE) datum = {"foo": 42, "sentinel": 24} item_loaded = schema.load(datum) assert item_loaded["foo"] == 42 assert item_loaded["_post_load"] == 24 item_dumped = schema.dump(datum) assert item_dumped["foo"] == 42 assert item_dumped["_post_dump"] == 24 def test_pass_original_many(self): class MySchema(Schema): foo = fields.Raw() @post_load(pass_many=True, pass_original=True) def post_load(self, data, original, many, **kwargs): if many: ret = [] for item, orig_item in zip(data, original): item["_post_load"] = orig_item["sentinel"] ret.append(item) else: ret = data.copy() ret["_post_load"] = original["sentinel"] return ret @post_dump(pass_many=True, pass_original=True) def post_dump(self, data, original, many, **kwargs): if many: ret = [] for item, orig_item in zip(data, original): item["_post_dump"] = orig_item["sentinel"] ret.append(item) else: ret = data.copy() ret["_post_dump"] = original["sentinel"] return ret schema = MySchema(unknown=EXCLUDE) data = [{"foo": 42, "sentinel": 24}, {"foo": 424, "sentinel": 242}] items_loaded = schema.load(data, many=True) assert items_loaded == [ {"foo": 42, "_post_load": 24}, {"foo": 424, "_post_load": 242}, ] test_values = [e["_post_load"] for e in items_loaded] assert test_values == [24, 242] items_dumped = schema.dump(data, many=True) assert items_dumped == [ {"foo": 42, "_post_dump": 24}, {"foo": 424, "_post_dump": 242}, ] # Also check load/dump of single item datum = {"foo": 42, "sentinel": 24} item_loaded = schema.load(datum, many=False) assert item_loaded == {"foo": 42, "_post_load": 24} item_dumped = schema.dump(datum, many=False) assert item_dumped == {"foo": 42, "_post_dump": 24} def test_decorated_processor_inheritance(): class ParentSchema(Schema): @post_dump def inherited(self, item, **kwargs): item["inherited"] = "inherited" return item @post_dump def overridden(self, item, **kwargs): item["overridden"] = "base" return item @post_dump def deleted(self, item, **kwargs): item["deleted"] = "retained" return item class ChildSchema(ParentSchema): @post_dump def overridden(self, item, **kwargs): item["overridden"] = "overridden" return item deleted = None parent_dumped = ParentSchema().dump({}) assert parent_dumped == { "inherited": "inherited", "overridden": "base", "deleted": "retained", } child_dumped = ChildSchema().dump({}) assert child_dumped == {"inherited": "inherited", "overridden": "overridden"} # https://github.com/marshmallow-code/marshmallow/issues/229#issuecomment-138949436 def test_pre_dump_is_invoked_before_implicit_field_generation(): class Foo(Schema): field = fields.Integer() @pre_dump def hook(self, data, **kwargs): data["generated_field"] = 7 return data class Meta: # Removing generated_field from here drops it from the output fields = ("field", "generated_field") assert Foo().dump({"field": 5}) == {"field": 5, "generated_field": 7} class ValidatesSchema(Schema): foo = fields.Int() @validates("foo") def validate_foo(self, value): if value != 42: raise ValidationError("The answer to life the universe and everything.") class TestValidatesDecorator: def test_validates(self): class VSchema(Schema): s = fields.String() @validates("s") def validate_string(self, data): raise ValidationError("nope") with pytest.raises(ValidationError) as excinfo: VSchema().load({"s": "bar"}) assert excinfo.value.messages == {"s": ["nope"]} # Regression test for https://github.com/marshmallow-code/marshmallow/issues/350 def test_validates_with_attribute(self): class S1(Schema): s = fields.String(attribute="string_name") @validates("s") def validate_string(self, data): raise ValidationError("nope") with pytest.raises(ValidationError) as excinfo: S1().load({"s": "foo"}) assert excinfo.value.messages == {"s": ["nope"]} with pytest.raises(ValidationError): S1(many=True).load([{"s": "foo"}]) def test_validates_decorator(self): schema = ValidatesSchema() errors = schema.validate({"foo": 41}) assert "foo" in errors assert errors["foo"][0] == "The answer to life the universe and everything." errors = schema.validate({"foo": 42}) assert errors == {} errors = schema.validate([{"foo": 42}, {"foo": 43}], many=True) assert "foo" in errors[1] assert len(errors[1]["foo"]) == 1 assert errors[1]["foo"][0] == "The answer to life the universe and everything." errors = schema.validate([{"foo": 42}, {"foo": 42}], many=True) assert errors == {} errors = schema.validate({}) assert errors == {} with pytest.raises(ValidationError) as excinfo: schema.load({"foo": 41}) errors = excinfo.value.messages result = excinfo.value.valid_data assert errors assert result == {} with pytest.raises(ValidationError) as excinfo: schema.load([{"foo": 42}, {"foo": 43}], many=True) errors = excinfo.value.messages result = excinfo.value.valid_data assert len(result) == 2 assert result[0] == {"foo": 42} assert result[1] == {} assert 1 in errors assert "foo" in errors[1] assert errors[1]["foo"] == ["The answer to life the universe and everything."] def test_field_not_present(self): class BadSchema(ValidatesSchema): @validates("bar") def validate_bar(self, value): raise ValidationError("Never raised.") schema = BadSchema() with pytest.raises(ValueError, match='"bar" field does not exist.'): schema.validate({"foo": 42}) def test_precedence(self): class Schema2(ValidatesSchema): foo = fields.Int(validate=lambda n: n != 42) bar = fields.Int(validate=lambda n: n == 1) @validates("bar") def validate_bar(self, value): if value != 2: raise ValidationError("Must be 2") schema = Schema2() errors = schema.validate({"foo": 42}) assert "foo" in errors assert len(errors["foo"]) == 1 assert "Invalid value." in errors["foo"][0] errors = schema.validate({"bar": 3}) assert "bar" in errors assert len(errors["bar"]) == 1 assert "Invalid value." in errors["bar"][0] errors = schema.validate({"bar": 1}) assert "bar" in errors assert len(errors["bar"]) == 1 assert errors["bar"][0] == "Must be 2" # Regression test for https://github.com/marshmallow-code/marshmallow/issues/748 def test_validates_with_data_key(self): class BadSchema(Schema): foo = fields.String(data_key="foo-name") @validates("foo") def validate_string(self, data): raise ValidationError("nope") schema = BadSchema() errors = schema.validate({"foo-name": "data"}) assert "foo-name" in errors assert errors["foo-name"] == ["nope"] schema = BadSchema() errors = schema.validate( [{"foo-name": "data"}, {"foo-name": "data2"}], many=True ) assert errors == {0: {"foo-name": ["nope"]}, 1: {"foo-name": ["nope"]}} class TestValidatesSchemaDecorator: def test_validator_nested_many_invalid_data(self): class NestedSchema(Schema): foo = fields.Int(required=True) class MySchema(Schema): nested = fields.Nested(NestedSchema, required=True, many=True) schema = MySchema() errors = schema.validate({"nested": [1]}) assert errors assert "nested" in errors assert 0 in errors["nested"] assert errors["nested"][0] == {"_schema": ["Invalid input type."]} def test_validator_nested_many_schema_error(self): class NestedSchema(Schema): foo = fields.Int(required=True) @validates_schema def validate_schema(self, data, **kwargs): raise ValidationError("This will never work.") class MySchema(Schema): nested = fields.Nested(NestedSchema, required=True, many=True) schema = MySchema() errors = schema.validate({"nested": [{"foo": 1}]}) assert errors assert "nested" in errors assert 0 in errors["nested"] assert errors["nested"][0] == {"_schema": ["This will never work."]} def test_validator_nested_many_field_error(self): class NestedSchema(Schema): foo = fields.Int(required=True) @validates_schema def validate_schema(self, data, **kwargs): raise ValidationError("This will never work.", "foo") class MySchema(Schema): nested = fields.Nested(NestedSchema, required=True, many=True) schema = MySchema() errors = schema.validate({"nested": [{"foo": 1}]}) assert errors assert "nested" in errors assert 0 in errors["nested"] assert errors["nested"][0] == {"foo": ["This will never work."]} @pytest.mark.parametrize("data", ([{"foo": 1, "bar": 2}],)) @pytest.mark.parametrize( ("pass_many", "expected_data", "expected_original_data"), ( [True, [{"foo": 1}], [{"foo": 1, "bar": 2}]], [False, {"foo": 1}, {"foo": 1, "bar": 2}], ), ) def test_validator_nested_many_pass_original_and_pass_many( self, pass_many, data, expected_data, expected_original_data ): class NestedSchema(Schema): foo = fields.Int(required=True) @validates_schema(pass_many=pass_many, pass_original=True) def validate_schema(self, data, original_data, many, **kwargs): assert data == expected_data assert original_data == expected_original_data assert many is True raise ValidationError("Method called") class MySchema(Schema): nested = fields.Nested( NestedSchema, required=True, many=True, unknown=EXCLUDE ) schema = MySchema() errors = schema.validate({"nested": data}) error = errors["nested"] if pass_many else errors["nested"][0] assert error["_schema"][0] == "Method called" def test_decorated_validators(self): class MySchema(Schema): foo = fields.Int() bar = fields.Int() @validates_schema def validate_schema(self, data, **kwargs): if data["foo"] <= 3: raise ValidationError("Must be greater than 3") @validates_schema(pass_many=True) def validate_raw(self, data, many, **kwargs): if many: assert type(data) is list if len(data) < 2: raise ValidationError("Must provide at least 2 items") @validates_schema def validate_bar(self, data, **kwargs): if "bar" in data and data["bar"] < 0: raise ValidationError("bar must not be negative", "bar") schema = MySchema() errors = schema.validate({"foo": 3}) assert "_schema" in errors assert errors["_schema"][0] == "Must be greater than 3" errors = schema.validate([{"foo": 4}], many=True) assert "_schema" in errors assert len(errors["_schema"]) == 1 assert errors["_schema"][0] == "Must provide at least 2 items" errors = schema.validate({"foo": 4, "bar": -1}) assert "bar" in errors assert len(errors["bar"]) == 1 assert errors["bar"][0] == "bar must not be negative" def test_multiple_validators(self): class MySchema(Schema): foo = fields.Int() bar = fields.Int() @validates_schema def validate_schema(self, data, **kwargs): if data["foo"] <= 3: raise ValidationError("Must be greater than 3") @validates_schema def validate_bar(self, data, **kwargs): if "bar" in data and data["bar"] < 0: raise ValidationError("bar must not be negative") schema = MySchema() errors = schema.validate({"foo": 3, "bar": -1}) assert type(errors) is dict assert "_schema" in errors assert len(errors["_schema"]) == 2 assert "Must be greater than 3" in errors["_schema"] assert "bar must not be negative" in errors["_schema"] errors = schema.validate([{"foo": 3, "bar": -1}, {"foo": 3}], many=True) assert type(errors) is dict assert "_schema" in errors[0] assert len(errors[0]["_schema"]) == 2 assert "Must be greater than 3" in errors[0]["_schema"] assert "bar must not be negative" in errors[0]["_schema"] assert len(errors[1]["_schema"]) == 1 assert "Must be greater than 3" in errors[0]["_schema"] def test_multiple_validators_merge_dict_errors(self): class NestedSchema(Schema): foo = fields.Int() bar = fields.Int() class MySchema(Schema): nested = fields.Nested(NestedSchema) @validates_schema def validate_nested_foo(self, data, **kwargs): raise ValidationError({"nested": {"foo": ["Invalid foo"]}}) @validates_schema def validate_nested_bar_1(self, data, **kwargs): raise ValidationError({"nested": {"bar": ["Invalid bar 1"]}}) @validates_schema def validate_nested_bar_2(self, data, **kwargs): raise ValidationError({"nested": {"bar": ["Invalid bar 2"]}}) with pytest.raises(ValidationError) as excinfo: MySchema().load({"nested": {"foo": 1, "bar": 2}}) assert excinfo.value.messages == { "nested": { "foo": ["Invalid foo"], "bar": ["Invalid bar 1", "Invalid bar 2"], } } def test_passing_original_data(self): class MySchema(Schema): foo = fields.Int() bar = fields.Int() @validates_schema(pass_original=True) def validate_original(self, data, original_data, partial, **kwargs): if isinstance(original_data, dict) and isinstance( original_data["foo"], str ): raise ValidationError("foo cannot be a string") @validates_schema(pass_many=True, pass_original=True) def validate_original_bar(self, data, original_data, many, **kwargs): def check(datum): if isinstance(datum, dict) and isinstance(datum["bar"], str): raise ValidationError("bar cannot be a string") if many: for each in original_data: check(each) else: check(original_data) schema = MySchema() errors = schema.validate({"foo": "4", "bar": 12}) assert errors["_schema"] == ["foo cannot be a string"] errors = schema.validate({"foo": 4, "bar": "42"}) assert errors["_schema"] == ["bar cannot be a string"] errors = schema.validate([{"foo": 4, "bar": "42"}], many=True) assert errors["_schema"] == ["bar cannot be a string"] def test_allow_reporting_field_errors_in_schema_validator(self): class NestedSchema(Schema): baz = fields.Int(required=True) class MySchema(Schema): foo = fields.Int(required=True) bar = fields.Nested(NestedSchema, required=True) bam = fields.Int(required=True) @validates_schema(skip_on_field_errors=True) def consistency_validation(self, data, **kwargs): errors = {} if data["bar"]["baz"] != data["foo"]: errors["bar"] = {"baz": "Non-matching value"} if data["bam"] > data["foo"]: errors["bam"] = "Value should be less than foo" if errors: raise ValidationError(errors) schema = MySchema() errors = schema.validate({"foo": 2, "bar": {"baz": 5}, "bam": 6}) assert errors["bar"]["baz"] == "Non-matching value" assert errors["bam"] == "Value should be less than foo" # https://github.com/marshmallow-code/marshmallow/issues/273 def test_allow_arbitrary_field_names_in_error(self): class MySchema(Schema): @validates_schema def validator(self, data, **kwargs): raise ValidationError("Error message", "arbitrary_key") errors = MySchema().validate({}) assert errors["arbitrary_key"] == ["Error message"] def test_skip_on_field_errors(self): class MySchema(Schema): foo = fields.Int(required=True, validate=lambda n: n == 3) bar = fields.Int(required=True) @validates_schema(skip_on_field_errors=True) def validate_schema(self, data, **kwargs): if data["foo"] != data["bar"]: raise ValidationError("Foo and bar must be equal.") @validates_schema(skip_on_field_errors=True, pass_many=True) def validate_many(self, data, many, **kwargs): if many: assert type(data) is list if len(data) < 2: raise ValidationError("Must provide at least 2 items") schema = MySchema() # check that schema errors still occur with no field errors errors = schema.validate({"foo": 3, "bar": 4}) assert "_schema" in errors assert errors["_schema"][0] == "Foo and bar must be equal." errors = schema.validate([{"foo": 3, "bar": 3}], many=True) assert "_schema" in errors assert errors["_schema"][0] == "Must provide at least 2 items" # check that schema errors don't occur when field errors do errors = schema.validate({"foo": 3, "bar": "not an int"}) assert "bar" in errors assert "_schema" not in errors errors = schema.validate({"foo": 2, "bar": 2}) assert "foo" in errors assert "_schema" not in errors errors = schema.validate([{"foo": 3, "bar": "not an int"}], many=True) assert "bar" in errors[0] assert "_schema" not in errors # https://github.com/marshmallow-code/marshmallow/issues/2170 def test_data_key_is_used_in_errors_dict(self): class MySchema(Schema): foo = fields.Int(data_key="fooKey") @validates("foo") def validate_foo(self, value, **kwargs): raise ValidationError("from validates") @validates_schema(skip_on_field_errors=False) def validate_schema(self, data, **kwargs): raise ValidationError("from validates_schema str", field_name="foo") @validates_schema(skip_on_field_errors=False) def validate_schema2(self, data, **kwargs): raise ValidationError({"fooKey": "from validates_schema dict"}) with pytest.raises(ValidationError) as excinfo: MySchema().load({"fooKey": 42}) exc = excinfo.value assert exc.messages == { "fooKey": [ "from validates", "from validates_schema str", "from validates_schema dict", ] } def test_decorator_error_handling(): class ExampleSchema(Schema): foo = fields.Int() bar = fields.Int() @pre_load() def pre_load_error1(self, item, **kwargs): if item["foo"] != 0: return item errors = {"foo": ["preloadmsg1"], "bar": ["preloadmsg2", "preloadmsg3"]} raise ValidationError(errors) @pre_load() def pre_load_error2(self, item, **kwargs): if item["foo"] != 4: return item raise ValidationError("preloadmsg1", "foo") @pre_load() def pre_load_error3(self, item, **kwargs): if item["foo"] != 8: return item raise ValidationError("preloadmsg1") @post_load() def post_load_error1(self, item, **kwargs): if item["foo"] != 1: return item errors = {"foo": ["postloadmsg1"], "bar": ["postloadmsg2", "postloadmsg3"]} raise ValidationError(errors) @post_load() def post_load_error2(self, item, **kwargs): if item["foo"] != 5: return item raise ValidationError("postloadmsg1", "foo") def make_item(foo, bar): data = schema.load({"foo": foo, "bar": bar}) assert data is not None return data schema = ExampleSchema() with pytest.raises(ValidationError) as excinfo: schema.load({"foo": 0, "bar": 1}) errors = excinfo.value.messages assert "foo" in errors assert len(errors["foo"]) == 1 assert errors["foo"][0] == "preloadmsg1" assert "bar" in errors assert len(errors["bar"]) == 2 assert "preloadmsg2" in errors["bar"] assert "preloadmsg3" in errors["bar"] with pytest.raises(ValidationError) as excinfo: schema.load({"foo": 1, "bar": 1}) errors = excinfo.value.messages assert "foo" in errors assert len(errors["foo"]) == 1 assert errors["foo"][0] == "postloadmsg1" assert "bar" in errors assert len(errors["bar"]) == 2 assert "postloadmsg2" in errors["bar"] assert "postloadmsg3" in errors["bar"] with pytest.raises(ValidationError) as excinfo: schema.load({"foo": 4, "bar": 1}) errors = excinfo.value.messages assert len(errors) == 1 assert "foo" in errors assert len(errors["foo"]) == 1 assert errors["foo"][0] == "preloadmsg1" with pytest.raises(ValidationError) as excinfo: schema.load({"foo": 5, "bar": 1}) errors = excinfo.value.messages assert len(errors) == 1 assert "foo" in errors assert len(errors["foo"]) == 1 assert errors["foo"][0] == "postloadmsg1" with pytest.raises(ValidationError) as excinfo: schema.load({"foo": 8, "bar": 1}) errors = excinfo.value.messages assert len(errors) == 1 assert "_schema" in errors assert len(errors["_schema"]) == 1 assert errors["_schema"][0] == "preloadmsg1" @pytest.mark.parametrize("decorator", [pre_load, post_load]) def test_decorator_error_handling_with_load(decorator): class ExampleSchema(Schema): @decorator def raise_value_error(self, item, **kwargs): raise ValidationError({"foo": "error"}) schema = ExampleSchema() with pytest.raises(ValidationError) as exc: schema.load({}) assert exc.value.messages == {"foo": "error"} schema.dump(object()) @pytest.mark.parametrize("decorator", [pre_load, post_load]) def test_decorator_error_handling_with_load_dict_error(decorator): class ExampleSchema(Schema): @decorator def raise_value_error(self, item, **kwargs): raise ValidationError({"foo": "error"}, "nested_field") schema = ExampleSchema() with pytest.raises(ValidationError) as exc: schema.load({}) assert exc.value.messages == {"nested_field": {"foo": "error"}} schema.dump(object()) @pytest.mark.parametrize("decorator", [pre_dump, post_dump]) def test_decorator_error_handling_with_dump(decorator): class ExampleSchema(Schema): @decorator def raise_value_error(self, item, **kwargs): raise ValidationError({"foo": "error"}) schema = ExampleSchema() with pytest.raises(ValidationError) as exc: schema.dump(object()) assert exc.value.messages == {"foo": "error"} schema.load({}) class Nested: def __init__(self, foo): self.foo = foo class Example: def __init__(self, nested): self.nested = nested example = Example(nested=[Nested(x) for x in range(1)]) @pytest.mark.parametrize( ("data", "expected_data", "expected_original_data"), ([example, {"foo": 0}, example.nested[0]],), ) def test_decorator_post_dump_with_nested_original_and_pass_many( data, expected_data, expected_original_data ): class NestedSchema(Schema): foo = fields.Int(required=True) @post_dump(pass_many=False, pass_original=True) def check_pass_original_when_pass_many_false( self, data, original_data, **kwargs ): assert data == expected_data assert original_data == expected_original_data return data @post_dump(pass_many=True, pass_original=True) def check_pass_original_when_pass_many_true( self, data, original_data, many, **kwargs ): assert many is True assert data == [expected_data] assert original_data == [expected_original_data] return data class ExampleSchema(Schema): nested = fields.Nested(NestedSchema, required=True, many=True) schema = ExampleSchema() assert schema.dump(data) == {"nested": [{"foo": 0}]} @pytest.mark.parametrize( ("data", "expected_data", "expected_original_data"), ([{"nested": [{"foo": 0}]}, {"foo": 0}, {"foo": 0}],), ) def test_decorator_post_load_with_nested_original_and_pass_many( data, expected_data, expected_original_data ): class NestedSchema(Schema): foo = fields.Int(required=True) @post_load(pass_many=False, pass_original=True) def check_pass_original_when_pass_many_false( self, data, original_data, **kwargs ): assert data == expected_data assert original_data == expected_original_data return data @post_load(pass_many=True, pass_original=True) def check_pass_original_when_pass_many_true( self, data, original_data, many, **kwargs ): assert many is True assert data == [expected_data] assert original_data == [expected_original_data] return data class ExampleSchema(Schema): nested = fields.Nested(NestedSchema, required=True, many=True) schema = ExampleSchema() assert schema.load(data) == data # https://github.com/marshmallow-code/marshmallow/issues/1755 def test_post_load_method_that_appends_to_data(): class MySchema(Schema): foo = fields.Int() @post_load(pass_many=True) def append_to_data(self, data, **kwargs): data.append({"foo": 42}) return data @post_load(pass_many=False, pass_original=True) def noop(self, data, original_data, **kwargs): if original_data is None: # added item assert data == {"foo": 42} else: assert original_data == {"foo": 24} assert data == {"foo": 24} return data schema = MySchema(many=True) assert schema.load([{"foo": 24}]) == [{"foo": 24}, {"foo": 42}] marshmallow-3.26.1/tests/test_deserialization.py000066400000000000000000002543201475016050200221000ustar00rootroot00000000000000import datetime as dt import decimal import ipaddress import math import uuid from unittest.mock import patch import pytest from marshmallow import EXCLUDE, INCLUDE, RAISE, Schema, fields, validate from marshmallow.exceptions import ValidationError from marshmallow.validate import Equal from marshmallow.warnings import ( ChangedInMarshmallow4Warning, RemovedInMarshmallow4Warning, ) from tests.base import ( ALL_FIELDS, DateEnum, GenderEnum, HairColorEnum, assert_date_equal, assert_time_equal, central, ) class MockDateTimeOverflowError(dt.datetime): """Used to simulate the possible OverflowError of datetime.fromtimestamp""" def fromtimestamp(self, *args, **kwargs): raise OverflowError class MockDateTimeOSError(dt.datetime): """Used to simulate the possible OSError of datetime.fromtimestamp""" def fromtimestamp(self, *args, **kwargs): raise OSError class TestDeserializingNone: @pytest.mark.parametrize("FieldClass", ALL_FIELDS) def test_fields_allow_none_deserialize_to_none(self, FieldClass): field = FieldClass(allow_none=True) assert field.deserialize(None) is None # https://github.com/marshmallow-code/marshmallow/issues/111 @pytest.mark.parametrize("FieldClass", ALL_FIELDS) def test_fields_dont_allow_none_by_default(self, FieldClass): field = FieldClass() with pytest.raises(ValidationError, match="Field may not be null."): field.deserialize(None) def test_allow_none_is_true_if_missing_is_true(self): field = fields.Raw(load_default=None) assert field.allow_none is True assert field.deserialize(None) is None def test_list_field_deserialize_none_to_none(self): field = fields.List(fields.String(allow_none=True), allow_none=True) assert field.deserialize(None) is None def test_tuple_field_deserialize_none_to_none(self): field = fields.Tuple([fields.String()], allow_none=True) assert field.deserialize(None) is None def test_list_of_nested_allow_none_deserialize_none_to_none(self): field = fields.List(fields.Nested(Schema(), allow_none=True)) assert field.deserialize([None]) == [None] def test_list_of_nested_non_allow_none_deserialize_none_to_validation_error(self): field = fields.List(fields.Nested(Schema(), allow_none=False)) with pytest.raises(ValidationError): field.deserialize([None]) class TestFieldDeserialization: def test_float_field_deserialization(self): field = fields.Float() assert math.isclose(field.deserialize("12.3"), 12.3) assert math.isclose(field.deserialize(12.3), 12.3) @pytest.mark.parametrize("in_val", ["bad", "", {}, True, False]) def test_invalid_float_field_deserialization(self, in_val): field = fields.Float() with pytest.raises(ValidationError) as excinfo: field.deserialize(in_val) assert excinfo.value.args[0] == "Not a valid number." def test_float_field_overflow(self): field = fields.Float() with pytest.raises(ValidationError) as excinfo: field.deserialize(2**1024) assert excinfo.value.args[0] == "Number too large." def test_integer_field_deserialization(self): field = fields.Integer() assert field.deserialize("42") == 42 with pytest.raises(ValidationError) as excinfo: field.deserialize("42.0") assert excinfo.value.args[0] == "Not a valid integer." with pytest.raises(ValidationError): field.deserialize("bad") assert excinfo.value.args[0] == "Not a valid integer." with pytest.raises(ValidationError): field.deserialize({}) assert excinfo.value.args[0] == "Not a valid integer." def test_strict_integer_field_deserialization(self): field = fields.Integer(strict=True) assert field.deserialize(42) == 42 with pytest.raises(ValidationError) as excinfo: field.deserialize(42.0) assert excinfo.value.args[0] == "Not a valid integer." with pytest.raises(ValidationError) as excinfo: field.deserialize(decimal.Decimal("42.0")) assert excinfo.value.args[0] == "Not a valid integer." with pytest.raises(ValidationError) as excinfo: field.deserialize("42") assert excinfo.value.args[0] == "Not a valid integer." def test_decimal_field_deserialization(self): m1 = 12 m2 = "12.355" m3 = decimal.Decimal(1) m4 = 3.14 m5 = "abc" m6 = [1, 2] field = fields.Decimal() assert isinstance(field.deserialize(m1), decimal.Decimal) assert field.deserialize(m1) == decimal.Decimal(12) assert isinstance(field.deserialize(m2), decimal.Decimal) assert field.deserialize(m2) == decimal.Decimal("12.355") assert isinstance(field.deserialize(m3), decimal.Decimal) assert field.deserialize(m3) == decimal.Decimal(1) assert isinstance(field.deserialize(m4), decimal.Decimal) assert field.deserialize(m4).as_tuple() == (0, (3, 1, 4), -2) with pytest.raises(ValidationError) as excinfo: field.deserialize(m5) assert excinfo.value.args[0] == "Not a valid number." with pytest.raises(ValidationError) as excinfo: field.deserialize(m6) assert excinfo.value.args[0] == "Not a valid number." def test_decimal_field_with_places(self): m1 = 12 m2 = "12.355" m3 = decimal.Decimal(1) m4 = "abc" m5 = [1, 2] field = fields.Decimal(1) assert isinstance(field.deserialize(m1), decimal.Decimal) assert field.deserialize(m1) == decimal.Decimal(12) assert isinstance(field.deserialize(m2), decimal.Decimal) assert field.deserialize(m2) == decimal.Decimal("12.4") assert isinstance(field.deserialize(m3), decimal.Decimal) assert field.deserialize(m3) == decimal.Decimal(1) with pytest.raises(ValidationError) as excinfo: field.deserialize(m4) assert excinfo.value.args[0] == "Not a valid number." with pytest.raises(ValidationError) as excinfo: field.deserialize(m5) assert excinfo.value.args[0] == "Not a valid number." def test_decimal_field_with_places_and_rounding(self): m1 = 12 m2 = "12.355" m3 = decimal.Decimal(1) m4 = "abc" m5 = [1, 2] field = fields.Decimal(1, decimal.ROUND_DOWN) assert isinstance(field.deserialize(m1), decimal.Decimal) assert field.deserialize(m1) == decimal.Decimal(12) assert isinstance(field.deserialize(m2), decimal.Decimal) assert field.deserialize(m2) == decimal.Decimal("12.3") assert isinstance(field.deserialize(m3), decimal.Decimal) assert field.deserialize(m3) == decimal.Decimal(1) with pytest.raises(ValidationError): field.deserialize(m4) with pytest.raises(ValidationError): field.deserialize(m5) def test_decimal_field_deserialization_string(self): m1 = 12 m2 = "12.355" m3 = decimal.Decimal(1) m4 = "abc" m5 = [1, 2] field = fields.Decimal(as_string=True) assert isinstance(field.deserialize(m1), decimal.Decimal) assert field.deserialize(m1) == decimal.Decimal(12) assert isinstance(field.deserialize(m2), decimal.Decimal) assert field.deserialize(m2) == decimal.Decimal("12.355") assert isinstance(field.deserialize(m3), decimal.Decimal) assert field.deserialize(m3) == decimal.Decimal(1) with pytest.raises(ValidationError): field.deserialize(m4) with pytest.raises(ValidationError): field.deserialize(m5) def test_decimal_field_special_values(self): m1 = "-NaN" m2 = "NaN" m3 = "-sNaN" m4 = "sNaN" m5 = "-Infinity" m6 = "Infinity" m7 = "-0" field = fields.Decimal(places=2, allow_nan=True) m1d = field.deserialize(m1) assert isinstance(m1d, decimal.Decimal) assert m1d.is_qnan() assert not m1d.is_signed() m2d = field.deserialize(m2) assert isinstance(m2d, decimal.Decimal) assert m2d.is_qnan() assert not m2d.is_signed() m3d = field.deserialize(m3) assert isinstance(m3d, decimal.Decimal) assert m3d.is_qnan() assert not m3d.is_signed() m4d = field.deserialize(m4) assert isinstance(m4d, decimal.Decimal) assert m4d.is_qnan() assert not m4d.is_signed() m5d = field.deserialize(m5) assert isinstance(m5d, decimal.Decimal) assert m5d.is_infinite() assert m5d.is_signed() m6d = field.deserialize(m6) assert isinstance(m6d, decimal.Decimal) assert m6d.is_infinite() assert not m6d.is_signed() m7d = field.deserialize(m7) assert isinstance(m7d, decimal.Decimal) assert m7d.is_zero() assert m7d.is_signed() def test_decimal_field_special_values_not_permitted(self): m1 = "-NaN" m2 = "NaN" m3 = "-sNaN" m4 = "sNaN" m5 = "-Infinity" m6 = "Infinity" m7 = "-0" field = fields.Decimal(places=2) with pytest.raises(ValidationError) as excinfo: field.deserialize(m1) assert str(excinfo.value.args[0]) == ( "Special numeric values (nan or infinity) are not permitted." ) with pytest.raises(ValidationError): field.deserialize(m2) with pytest.raises(ValidationError): field.deserialize(m3) with pytest.raises(ValidationError): field.deserialize(m4) with pytest.raises(ValidationError): field.deserialize(m5) with pytest.raises(ValidationError): field.deserialize(m6) m7d = field.deserialize(m7) assert isinstance(m7d, decimal.Decimal) assert m7d.is_zero() assert m7d.is_signed() @pytest.mark.parametrize("allow_nan", (None, False, True)) @pytest.mark.parametrize("value", ("nan", "-nan", "inf", "-inf")) def test_float_field_allow_nan(self, value, allow_nan): if allow_nan is None: # Test default case is False field = fields.Float() else: field = fields.Float(allow_nan=allow_nan) if allow_nan is True: res = field.deserialize(value) assert isinstance(res, float) if value.endswith("nan"): assert math.isnan(res) else: assert res == float(value) else: with pytest.raises(ValidationError) as excinfo: field.deserialize(value) assert str(excinfo.value.args[0]) == ( "Special numeric values (nan or infinity) are not permitted." ) def test_string_field_deserialization(self): field = fields.String() assert field.deserialize("foo") == "foo" assert field.deserialize(b"foo") == "foo" # https://github.com/marshmallow-code/marshmallow/issues/231 with pytest.raises(ValidationError) as excinfo: field.deserialize(42) assert excinfo.value.args[0] == "Not a valid string." with pytest.raises(ValidationError): field.deserialize({}) def test_boolean_field_deserialization(self): field = fields.Boolean() assert field.deserialize(True) is True assert field.deserialize(False) is False assert field.deserialize("True") is True assert field.deserialize("False") is False assert field.deserialize("true") is True assert field.deserialize("false") is False assert field.deserialize("1") is True assert field.deserialize("0") is False assert field.deserialize("on") is True assert field.deserialize("ON") is True assert field.deserialize("On") is True assert field.deserialize("off") is False assert field.deserialize("OFF") is False assert field.deserialize("Off") is False assert field.deserialize("y") is True assert field.deserialize("Y") is True assert field.deserialize("yes") is True assert field.deserialize("YES") is True assert field.deserialize("Yes") is True assert field.deserialize("n") is False assert field.deserialize("N") is False assert field.deserialize("no") is False assert field.deserialize("NO") is False assert field.deserialize("No") is False assert field.deserialize(1) is True assert field.deserialize(0) is False with pytest.raises(ValidationError) as excinfo: field.deserialize({}) assert excinfo.value.args[0] == "Not a valid boolean." with pytest.raises(ValidationError) as excinfo: field.deserialize(42) with pytest.raises(ValidationError) as excinfo: field.deserialize("invalid-string") def test_boolean_field_deserialization_with_custom_truthy_values(self): class MyBoolean(fields.Boolean): truthy = {"yep"} field = MyBoolean() assert field.deserialize("yep") is True field = fields.Boolean(truthy=("yep",)) assert field.deserialize("yep") is True assert field.deserialize(False) is False @pytest.mark.parametrize("in_val", ["notvalid", 123]) def test_boolean_field_deserialization_with_custom_truthy_values_invalid( self, in_val ): class MyBoolean(fields.Boolean): truthy = {"yep"} field = MyBoolean() with pytest.raises(ValidationError) as excinfo: field.deserialize(in_val) expected_msg = "Not a valid boolean." assert str(excinfo.value.args[0]) == expected_msg field = fields.Boolean(truthy=("yep",)) with pytest.raises(ValidationError) as excinfo: field.deserialize(in_val) expected_msg = "Not a valid boolean." assert str(excinfo.value.args[0]) == expected_msg field2 = MyBoolean(error_messages={"invalid": "bad input"}) with pytest.raises(ValidationError) as excinfo: field2.deserialize(in_val) assert str(excinfo.value.args[0]) == "bad input" field2 = fields.Boolean( truthy=("yep",), error_messages={"invalid": "bad input"} ) def test_boolean_field_deserialization_with_empty_truthy(self): field = fields.Boolean(truthy=()) assert field.deserialize("yep") is True assert field.deserialize(True) is True assert field.deserialize(False) is False def test_boolean_field_deserialization_with_custom_falsy_values(self): field = fields.Boolean(falsy=("nope",)) assert field.deserialize("nope") is False assert field.deserialize(True) is True def test_field_toggle_show_invalid_value_in_error_message(self): error_messages = {"invalid": "Not valid: {input}"} boolfield = fields.Boolean(error_messages=error_messages) with pytest.raises(ValidationError) as excinfo: boolfield.deserialize("notabool") assert str(excinfo.value.args[0]) == "Not valid: notabool" with pytest.warns(ChangedInMarshmallow4Warning): numfield = fields.Number(error_messages=error_messages) with pytest.raises(ValidationError) as excinfo: numfield.deserialize("notanum") assert str(excinfo.value.args[0]) == "Not valid: notanum" intfield = fields.Integer(error_messages=error_messages) with pytest.raises(ValidationError) as excinfo: intfield.deserialize("notanint") assert str(excinfo.value.args[0]) == "Not valid: notanint" date_error_messages = {"invalid": "Not a valid {obj_type}: {input}"} datefield = fields.DateTime(error_messages=date_error_messages) with pytest.raises(ValidationError) as excinfo: datefield.deserialize("notadate") assert str(excinfo.value.args[0]) == "Not a valid datetime: notadate" @pytest.mark.parametrize( "in_value", [ "not-a-datetime", 42, True, False, 0, "", [], "2018", "2018-01-01", dt.datetime.now().strftime("%H:%M:%S %Y-%m-%d"), dt.datetime.now().strftime("%m-%d-%Y %H:%M:%S"), ], ) def test_invalid_datetime_deserialization(self, in_value): field = fields.DateTime() with pytest.raises(ValidationError, match="Not a valid datetime."): field.deserialize(in_value) def test_custom_date_format_datetime_field_deserialization(self): # Datetime string with format "%H:%M:%S.%f %Y-%m-%d" datestring = "10:11:12.123456 2019-01-02" # Deserialization should fail when datestring is not of same format field = fields.DateTime(format="%d-%m-%Y %H:%M:%S") with pytest.raises(ValidationError, match="Not a valid datetime."): field.deserialize(datestring) field = fields.DateTime(format="%H:%M:%S.%f %Y-%m-%d") assert field.deserialize(datestring) == dt.datetime( 2019, 1, 2, 10, 11, 12, 123456 ) field = fields.NaiveDateTime(format="%H:%M:%S.%f %Y-%m-%d") assert field.deserialize(datestring) == dt.datetime( 2019, 1, 2, 10, 11, 12, 123456 ) field = fields.AwareDateTime(format="%H:%M:%S.%f %Y-%m-%d") with pytest.raises(ValidationError, match="Not a valid aware datetime."): field.deserialize(datestring) @pytest.mark.parametrize("fmt", ["rfc", "rfc822"]) @pytest.mark.parametrize( ("value", "expected", "aware"), [ ( "Sun, 10 Nov 2013 01:23:45 -0000", dt.datetime(2013, 11, 10, 1, 23, 45), False, ), ( "Sun, 10 Nov 2013 01:23:45 +0000", dt.datetime(2013, 11, 10, 1, 23, 45, tzinfo=dt.timezone.utc), True, ), ( "Sun, 10 Nov 2013 01:23:45 -0600", dt.datetime(2013, 11, 10, 1, 23, 45, tzinfo=central), True, ), ], ) def test_rfc_datetime_field_deserialization(self, fmt, value, expected, aware): field = fields.DateTime(format=fmt) assert field.deserialize(value) == expected field = fields.NaiveDateTime(format=fmt) if aware: with pytest.raises(ValidationError, match="Not a valid naive datetime."): field.deserialize(value) else: assert field.deserialize(value) == expected field = fields.AwareDateTime(format=fmt) if not aware: with pytest.raises(ValidationError, match="Not a valid aware datetime."): field.deserialize(value) else: assert field.deserialize(value) == expected @pytest.mark.parametrize("fmt", ["iso", "iso8601"]) @pytest.mark.parametrize( ("value", "expected", "aware"), [ ("2013-11-10T01:23:45", dt.datetime(2013, 11, 10, 1, 23, 45), False), ( "2013-11-10T01:23:45+00:00", dt.datetime(2013, 11, 10, 1, 23, 45, tzinfo=dt.timezone.utc), True, ), ( # Regression test for https://github.com/marshmallow-code/marshmallow/issues/1251 "2013-11-10T01:23:45.123+00:00", dt.datetime(2013, 11, 10, 1, 23, 45, 123000, tzinfo=dt.timezone.utc), True, ), ( "2013-11-10T01:23:45.123456+00:00", dt.datetime(2013, 11, 10, 1, 23, 45, 123456, tzinfo=dt.timezone.utc), True, ), ( "2013-11-10T01:23:45-06:00", dt.datetime(2013, 11, 10, 1, 23, 45, tzinfo=central), True, ), ], ) def test_iso_datetime_field_deserialization(self, fmt, value, expected, aware): field = fields.DateTime(format=fmt) assert field.deserialize(value) == expected field = fields.NaiveDateTime(format=fmt) if aware: with pytest.raises(ValidationError, match="Not a valid naive datetime."): field.deserialize(value) else: assert field.deserialize(value) == expected field = fields.AwareDateTime(format=fmt) if not aware: with pytest.raises(ValidationError, match="Not a valid aware datetime."): field.deserialize(value) else: assert field.deserialize(value) == expected @pytest.mark.parametrize( ("fmt", "value", "expected"), [ ("timestamp", 1384043025, dt.datetime(2013, 11, 10, 0, 23, 45)), ("timestamp", "1384043025", dt.datetime(2013, 11, 10, 0, 23, 45)), ("timestamp", 1384043025.12, dt.datetime(2013, 11, 10, 0, 23, 45, 120000)), ( "timestamp", 1384043025.123456, dt.datetime(2013, 11, 10, 0, 23, 45, 123456), ), ("timestamp", 1, dt.datetime(1970, 1, 1, 0, 0, 1)), ("timestamp_ms", 1384043025000, dt.datetime(2013, 11, 10, 0, 23, 45)), ("timestamp_ms", 1000, dt.datetime(1970, 1, 1, 0, 0, 1)), ], ) def test_timestamp_field_deserialization(self, fmt, value, expected): field = fields.DateTime(format=fmt) assert field.deserialize(value) == expected # By default, a datetime from a timestamp is never aware. field = fields.NaiveDateTime(format=fmt) assert field.deserialize(value) == expected field = fields.AwareDateTime(format=fmt) with pytest.raises(ValidationError, match="Not a valid aware datetime."): field.deserialize(value) # But it can be added by providing a default. field = fields.AwareDateTime(format=fmt, default_timezone=central) expected_aware = expected.replace(tzinfo=central) assert field.deserialize(value) == expected_aware @pytest.mark.parametrize("fmt", ["timestamp", "timestamp_ms"]) @pytest.mark.parametrize( "in_value", ["", "!@#", -1, dt.datetime(2013, 11, 10, 1, 23, 45)], ) def test_invalid_timestamp_field_deserialization(self, fmt, in_value): field = fields.DateTime(format=fmt) with pytest.raises(ValidationError, match="Not a valid datetime."): field.deserialize(in_value) # Regression test for https://github.com/marshmallow-code/marshmallow/pull/2102 @pytest.mark.parametrize("fmt", ["timestamp", "timestamp_ms"]) @pytest.mark.parametrize( "mock_fromtimestamp", [MockDateTimeOSError, MockDateTimeOverflowError] ) def test_oversized_timestamp_field_deserialization(self, fmt, mock_fromtimestamp): with patch("datetime.datetime", mock_fromtimestamp): field = fields.DateTime(format=fmt) with pytest.raises(ValidationError, match="Not a valid datetime."): field.deserialize(99999999999999999) @pytest.mark.parametrize( ("fmt", "timezone", "value", "expected"), [ ("iso", None, "2013-11-10T01:23:45", dt.datetime(2013, 11, 10, 1, 23, 45)), ( "iso", dt.timezone.utc, "2013-11-10T01:23:45+00:00", dt.datetime(2013, 11, 10, 1, 23, 45), ), ( "iso", central, "2013-11-10T01:23:45-03:00", dt.datetime(2013, 11, 9, 22, 23, 45), ), ( "rfc", None, "Sun, 10 Nov 2013 01:23:45 -0000", dt.datetime(2013, 11, 10, 1, 23, 45), ), ( "rfc", dt.timezone.utc, "Sun, 10 Nov 2013 01:23:45 +0000", dt.datetime(2013, 11, 10, 1, 23, 45), ), ( "rfc", central, "Sun, 10 Nov 2013 01:23:45 -0300", dt.datetime(2013, 11, 9, 22, 23, 45), ), ], ) def test_naive_datetime_with_timezone(self, fmt, timezone, value, expected): field = fields.NaiveDateTime(format=fmt, timezone=timezone) assert field.deserialize(value) == expected @pytest.mark.parametrize("timezone", (dt.timezone.utc, central)) @pytest.mark.parametrize( ("fmt", "value"), [("iso", "2013-11-10T01:23:45"), ("rfc", "Sun, 10 Nov 2013 01:23:45")], ) def test_aware_datetime_default_timezone(self, fmt, timezone, value): field = fields.AwareDateTime(format=fmt, default_timezone=timezone) assert field.deserialize(value) == dt.datetime( 2013, 11, 10, 1, 23, 45, tzinfo=timezone ) def test_time_field_deserialization(self): field = fields.Time() t = dt.time(1, 23, 45) t_formatted = t.isoformat() result = field.deserialize(t_formatted) assert isinstance(result, dt.time) assert_time_equal(result, t) # With microseconds t2 = dt.time(1, 23, 45, 6789) t2_formatted = t2.isoformat() result2 = field.deserialize(t2_formatted) assert_time_equal(result2, t2) @pytest.mark.parametrize("in_data", ["badvalue", "", [], 42]) def test_invalid_time_field_deserialization(self, in_data): field = fields.Time() with pytest.raises(ValidationError) as excinfo: field.deserialize(in_data) assert excinfo.value.args[0] == "Not a valid time." def test_custom_time_format_time_field_deserialization(self): # Time string with format "%f.%S:%M:%H" timestring = "123456.12:11:10" # Deserialization should fail when timestring is not of same format field = fields.Time(format="%S:%M:%H") with pytest.raises(ValidationError, match="Not a valid time."): field.deserialize(timestring) field = fields.Time(format="%f.%S:%M:%H") assert field.deserialize(timestring) == dt.time(10, 11, 12, 123456) @pytest.mark.parametrize("fmt", ["iso", "iso8601", None]) @pytest.mark.parametrize( ("value", "expected"), [ ("01:23:45", dt.time(1, 23, 45)), ("01:23:45+01:00", dt.time(1, 23, 45)), ("01:23:45.123", dt.time(1, 23, 45, 123000)), ("01:23:45.123456", dt.time(1, 23, 45, 123456)), ], ) def test_iso_time_field_deserialization(self, fmt, value, expected): if fmt is None: field = fields.Time() else: field = fields.Time(format=fmt) assert field.deserialize(value) == expected def test_invalid_timedelta_precision(self): with pytest.raises(ValueError, match='The precision must be "days",'): fields.TimeDelta("invalid") def test_timedelta_field_deserialization(self): field = fields.TimeDelta() result = field.deserialize("42") assert isinstance(result, dt.timedelta) assert result.days == 0 assert result.seconds == 42 assert result.microseconds == 0 field = fields.TimeDelta(fields.TimeDelta.SECONDS) result = field.deserialize(100000) assert result.days == 1 assert result.seconds == 13600 assert result.microseconds == 0 field = fields.TimeDelta(fields.TimeDelta.DAYS) result = field.deserialize("-42") assert isinstance(result, dt.timedelta) assert result.days == -42 assert result.seconds == 0 assert result.microseconds == 0 field = fields.TimeDelta(fields.TimeDelta.MICROSECONDS) result = field.deserialize(10**6 + 1) assert isinstance(result, dt.timedelta) assert result.days == 0 assert result.seconds == 1 assert result.microseconds == 1 field = fields.TimeDelta(fields.TimeDelta.MICROSECONDS) result = field.deserialize(86400 * 10**6 + 1) assert isinstance(result, dt.timedelta) assert result.days == 1 assert result.seconds == 0 assert result.microseconds == 1 field = fields.TimeDelta() result = field.deserialize(12.9) assert isinstance(result, dt.timedelta) assert result.days == 0 assert result.seconds == 12 assert result.microseconds == 0 field = fields.TimeDelta(fields.TimeDelta.WEEKS) result = field.deserialize(1) assert isinstance(result, dt.timedelta) assert result.days == 7 assert result.seconds == 0 assert result.microseconds == 0 field = fields.TimeDelta(fields.TimeDelta.HOURS) result = field.deserialize(25) assert isinstance(result, dt.timedelta) assert result.days == 1 assert result.seconds == 3600 assert result.microseconds == 0 field = fields.TimeDelta(fields.TimeDelta.MINUTES) result = field.deserialize(1441) assert isinstance(result, dt.timedelta) assert result.days == 1 assert result.seconds == 60 assert result.microseconds == 0 field = fields.TimeDelta(fields.TimeDelta.MILLISECONDS) result = field.deserialize(123456) assert isinstance(result, dt.timedelta) assert result.days == 0 assert result.seconds == 123 assert result.microseconds == 456000 total_microseconds_value = 322.0 field = fields.TimeDelta(fields.TimeDelta.MICROSECONDS, float) result = field.deserialize(total_microseconds_value) assert isinstance(result, dt.timedelta) unit_value = dt.timedelta(microseconds=1).total_seconds() assert math.isclose( result.total_seconds() / unit_value, total_microseconds_value ) total_microseconds_value = 322.12345 field = fields.TimeDelta(fields.TimeDelta.MICROSECONDS, float) result = field.deserialize(total_microseconds_value) assert isinstance(result, dt.timedelta) unit_value = dt.timedelta(microseconds=1).total_seconds() assert math.isclose( result.total_seconds() / unit_value, math.floor(total_microseconds_value) ) total_milliseconds_value = 322.223 field = fields.TimeDelta(fields.TimeDelta.MILLISECONDS, float) result = field.deserialize(total_milliseconds_value) assert isinstance(result, dt.timedelta) unit_value = dt.timedelta(milliseconds=1).total_seconds() assert math.isclose( result.total_seconds() / unit_value, total_milliseconds_value ) total_seconds_value = 322.223 field = fields.TimeDelta(fields.TimeDelta.SECONDS, float) result = field.deserialize(total_seconds_value) assert isinstance(result, dt.timedelta) assert math.isclose(result.total_seconds(), total_seconds_value) total_minutes_value = 322.223 field = fields.TimeDelta(fields.TimeDelta.MINUTES, float) result = field.deserialize(total_minutes_value) assert isinstance(result, dt.timedelta) unit_value = dt.timedelta(minutes=1).total_seconds() assert math.isclose(result.total_seconds() / unit_value, total_minutes_value) total_hours_value = 322.223 field = fields.TimeDelta(fields.TimeDelta.HOURS, float) result = field.deserialize(total_hours_value) assert isinstance(result, dt.timedelta) unit_value = dt.timedelta(hours=1).total_seconds() assert math.isclose(result.total_seconds() / unit_value, total_hours_value) total_days_value = 322.223 field = fields.TimeDelta(fields.TimeDelta.DAYS, float) result = field.deserialize(total_days_value) assert isinstance(result, dt.timedelta) unit_value = dt.timedelta(days=1).total_seconds() assert math.isclose(result.total_seconds() / unit_value, total_days_value) total_weeks_value = 322.223 field = fields.TimeDelta(fields.TimeDelta.WEEKS, float) result = field.deserialize(total_weeks_value) assert isinstance(result, dt.timedelta) unit_value = dt.timedelta(weeks=1).total_seconds() assert math.isclose(result.total_seconds() / unit_value, total_weeks_value) @pytest.mark.parametrize("in_value", ["", "badvalue", [], 9999999999]) def test_invalid_timedelta_field_deserialization(self, in_value): field = fields.TimeDelta(fields.TimeDelta.DAYS) with pytest.raises(ValidationError) as excinfo: field.deserialize(in_value) assert excinfo.value.args[0] == "Not a valid period of time." @pytest.mark.parametrize("format", (None, "%Y-%m-%d")) def test_date_field_deserialization(self, format): # noqa: A002 field = fields.Date(format=format) d = dt.date(2014, 8, 21) iso_date = d.isoformat() result = field.deserialize(iso_date) assert type(result) is dt.date assert_date_equal(result, d) @pytest.mark.parametrize( "in_value", ["", 123, [], dt.date(2014, 8, 21).strftime("%d-%m-%Y")] ) def test_invalid_date_field_deserialization(self, in_value): field = fields.Date() with pytest.raises(ValidationError) as excinfo: field.deserialize(in_value) msg = "Not a valid date." assert excinfo.value.args[0] == msg def test_dict_field_deserialization(self): data = {"foo": "bar"} field = fields.Dict() load = field.deserialize(data) assert load == {"foo": "bar"} # Check load is a distinct object load["foo"] = "baz" assert data["foo"] == "bar" with pytest.raises(ValidationError) as excinfo: field.deserialize("baddict") assert excinfo.value.args[0] == "Not a valid mapping type." def test_structured_dict_value_deserialization(self): field = fields.Dict(values=fields.List(fields.Str)) assert field.deserialize({"foo": ["bar", "baz"]}) == {"foo": ["bar", "baz"]} with pytest.raises(ValidationError) as excinfo: field.deserialize({"foo": [1, 2], "bar": "baz", "ham": ["spam"]}) assert excinfo.value.args[0] == { "foo": {"value": {0: ["Not a valid string."], 1: ["Not a valid string."]}}, "bar": {"value": ["Not a valid list."]}, } assert excinfo.value.valid_data == {"foo": [], "ham": ["spam"]} def test_structured_dict_key_deserialization(self): field = fields.Dict(keys=fields.Str) assert field.deserialize({"foo": "bar"}) == {"foo": "bar"} with pytest.raises(ValidationError) as excinfo: field.deserialize({1: "bar", "foo": "baz"}) assert excinfo.value.args[0] == {1: {"key": ["Not a valid string."]}} assert excinfo.value.valid_data == {"foo": "baz"} def test_structured_dict_key_value_deserialization(self): field = fields.Dict( keys=fields.Str( validate=[validate.Email(), validate.Regexp(r".*@test\.com$")] ), values=fields.Decimal, ) assert field.deserialize({"foo@test.com": 1}) == { "foo@test.com": decimal.Decimal(1) } with pytest.raises(ValidationError) as excinfo: field.deserialize({1: "bar"}) assert excinfo.value.args[0] == { 1: {"key": ["Not a valid string."], "value": ["Not a valid number."]} } with pytest.raises(ValidationError) as excinfo: field.deserialize({"foo@test.com": "bar"}) assert excinfo.value.args[0] == { "foo@test.com": {"value": ["Not a valid number."]} } assert excinfo.value.valid_data == {} with pytest.raises(ValidationError) as excinfo: field.deserialize({1: 1}) assert excinfo.value.args[0] == {1: {"key": ["Not a valid string."]}} assert excinfo.value.valid_data == {} with pytest.raises(ValidationError) as excinfo: field.deserialize({"foo": "bar"}) assert excinfo.value.args[0] == { "foo": { "key": [ "Not a valid email address.", "String does not match expected pattern.", ], "value": ["Not a valid number."], } } assert excinfo.value.valid_data == {} def test_url_field_deserialization(self): field = fields.Url() assert field.deserialize("https://duckduckgo.com") == "https://duckduckgo.com" with pytest.raises(ValidationError) as excinfo: field.deserialize("badurl") assert excinfo.value.args[0][0] == "Not a valid URL." # Relative URLS not allowed by default with pytest.raises(ValidationError) as excinfo: field.deserialize("/foo/bar") assert excinfo.value.args[0][0] == "Not a valid URL." # regression test for https://github.com/marshmallow-code/marshmallow/issues/1400 def test_url_field_non_list_validators(self): field = fields.Url(validate=(validate.Length(min=16),)) with pytest.raises(ValidationError, match="Shorter than minimum length"): field.deserialize("https://abc.def") def test_relative_url_field_deserialization(self): field = fields.Url(relative=True) assert field.deserialize("/foo/bar") == "/foo/bar" def test_url_field_schemes_argument(self): field = fields.URL() url = "ws://test.test" with pytest.raises(ValidationError): field.deserialize(url) field2 = fields.URL(schemes={"http", "https", "ws"}) assert field2.deserialize(url) == url def test_email_field_deserialization(self): field = fields.Email() assert field.deserialize("foo@bar.com") == "foo@bar.com" with pytest.raises(ValidationError) as excinfo: field.deserialize("invalidemail") assert excinfo.value.args[0][0] == "Not a valid email address." field = fields.Email(validate=[validate.Length(min=12)]) with pytest.raises(ValidationError) as excinfo: field.deserialize("foo@bar.com") assert excinfo.value.args[0][0] == "Shorter than minimum length 12." # regression test for https://github.com/marshmallow-code/marshmallow/issues/1400 def test_email_field_non_list_validators(self): field = fields.Email(validate=(validate.Length(min=9),)) with pytest.raises(ValidationError, match="Shorter than minimum length"): field.deserialize("a@bc.com") def test_function_field_deserialization_is_noop_by_default(self): field = fields.Function(lambda x: None) # Default is noop assert field.deserialize("foo") == "foo" assert field.deserialize(42) == 42 def test_function_field_deserialization_with_callable(self): field = fields.Function(lambda x: None, deserialize=lambda val: val.upper()) assert field.deserialize("foo") == "FOO" def test_function_field_deserialization_with_context(self): class Parent(Schema): pass field = fields.Function( lambda x: None, deserialize=lambda val, context: val.upper() + context["key"], ) with pytest.warns(RemovedInMarshmallow4Warning): field.parent = Parent(context={"key": "BAR"}) assert field.deserialize("foo") == "FOOBAR" def test_function_field_passed_deserialize_only_is_load_only(self): field = fields.Function(deserialize=lambda val: val.upper()) assert field.load_only is True def test_function_field_passed_deserialize_and_serialize_is_not_load_only(self): field = fields.Function( serialize=lambda val: val.lower(), deserialize=lambda val: val.upper() ) assert field.load_only is False def test_uuid_field_deserialization(self): field = fields.UUID() uuid_str = str(uuid.uuid4()) result = field.deserialize(uuid_str) assert isinstance(result, uuid.UUID) assert str(result) == uuid_str uuid4 = uuid.uuid4() result = field.deserialize(uuid4) assert isinstance(result, uuid.UUID) assert result == uuid4 uuid_bytes = b"]\xc7wW\x132O\xf9\xa5\xbe\x13\x1f\x02\x18\xda\xbf" result = field.deserialize(uuid_bytes) assert isinstance(result, uuid.UUID) assert result.bytes == uuid_bytes @pytest.mark.parametrize("in_value", ["malformed", 123, [], b"tooshort"]) def test_invalid_uuid_deserialization(self, in_value): field = fields.UUID() with pytest.raises(ValidationError) as excinfo: field.deserialize(in_value) assert excinfo.value.args[0] == "Not a valid UUID." def test_ip_field_deserialization(self): field = fields.IP() ipv4_str = "140.82.118.3" result = field.deserialize(ipv4_str) assert isinstance(result, ipaddress.IPv4Address) assert str(result) == ipv4_str ipv6_str = "2a00:1450:4001:824::200e" result = field.deserialize(ipv6_str) assert isinstance(result, ipaddress.IPv6Address) assert str(result) == ipv6_str @pytest.mark.parametrize( "in_value", ["malformed", 123, b"\x01\x02\03", "192.168", "192.168.0.1/24", "ff::aa:1::2"], ) def test_invalid_ip_deserialization(self, in_value): field = fields.IP() with pytest.raises(ValidationError) as excinfo: field.deserialize(in_value) assert excinfo.value.args[0] == "Not a valid IP address." def test_ipv4_field_deserialization(self): field = fields.IPv4() ipv4_str = "140.82.118.3" result = field.deserialize(ipv4_str) assert isinstance(result, ipaddress.IPv4Address) assert str(result) == ipv4_str @pytest.mark.parametrize( "in_value", [ "malformed", 123, b"\x01\x02\03", "192.168", "192.168.0.1/24", "2a00:1450:4001:81d::200e", ], ) def test_invalid_ipv4_deserialization(self, in_value): field = fields.IPv4() with pytest.raises(ValidationError) as excinfo: field.deserialize(in_value) assert excinfo.value.args[0] == "Not a valid IPv4 address." def test_ipv6_field_deserialization(self): field = fields.IPv6() ipv6_str = "2a00:1450:4001:824::200e" result = field.deserialize(ipv6_str) assert isinstance(result, ipaddress.IPv6Address) assert str(result) == ipv6_str def test_ipinterface_field_deserialization(self): field = fields.IPInterface() ipv4interface_str = "140.82.118.3/24" result = field.deserialize(ipv4interface_str) assert isinstance(result, ipaddress.IPv4Interface) assert str(result) == ipv4interface_str ipv6interface_str = "2a00:1450:4001:824::200e/128" result = field.deserialize(ipv6interface_str) assert isinstance(result, ipaddress.IPv6Interface) assert str(result) == ipv6interface_str @pytest.mark.parametrize( "in_value", [ "malformed", 123, b"\x01\x02\03", "192.168", "192.168.0.1/33", "ff::aa:1::2", "2a00:1450:4001:824::200e/129", ], ) def test_invalid_ipinterface_deserialization(self, in_value): field = fields.IPInterface() with pytest.raises(ValidationError) as excinfo: field.deserialize(in_value) assert excinfo.value.args[0] == "Not a valid IP interface." def test_ipv4interface_field_deserialization(self): field = fields.IPv4Interface() ipv4interface_str = "140.82.118.3/24" result = field.deserialize(ipv4interface_str) assert isinstance(result, ipaddress.IPv4Interface) assert str(result) == ipv4interface_str @pytest.mark.parametrize( "in_value", [ "malformed", 123, b"\x01\x02\03", "192.168", "192.168.0.1/33", "2a00:1450:4001:81d::200e", "2a00:1450:4001:824::200e/129", ], ) def test_invalid_ipv4interface_deserialization(self, in_value): field = fields.IPv4Interface() with pytest.raises(ValidationError) as excinfo: field.deserialize(in_value) assert excinfo.value.args[0] == "Not a valid IPv4 interface." def test_ipv6interface_field_deserialization(self): field = fields.IPv6Interface() ipv6interface_str = "2a00:1450:4001:824::200e/128" result = field.deserialize(ipv6interface_str) assert isinstance(result, ipaddress.IPv6Interface) assert str(result) == ipv6interface_str @pytest.mark.parametrize( "in_value", [ "malformed", 123, b"\x01\x02\03", "ff::aa:1::2", "192.168.0.1", "192.168.0.1/24", "2a00:1450:4001:824::200e/129", ], ) def test_invalid_ipv6interface_deserialization(self, in_value): field = fields.IPv6Interface() with pytest.raises(ValidationError) as excinfo: field.deserialize(in_value) assert excinfo.value.args[0] == "Not a valid IPv6 interface." def test_enum_field_by_symbol_deserialization(self): field = fields.Enum(GenderEnum) assert field.deserialize("male") == GenderEnum.male def test_enum_field_by_symbol_invalid_value(self): field = fields.Enum(GenderEnum) with pytest.raises( ValidationError, match="Must be one of: male, female, non_binary." ): field.deserialize("dummy") def test_enum_field_by_symbol_not_string(self): field = fields.Enum(GenderEnum) with pytest.raises(ValidationError, match="Not a valid string."): field.deserialize(12) def test_enum_field_by_value_true_deserialization(self): field = fields.Enum(HairColorEnum, by_value=True) assert field.deserialize("black hair") == HairColorEnum.black field = fields.Enum(GenderEnum, by_value=True) assert field.deserialize(1) == GenderEnum.male def test_enum_field_by_value_field_deserialization(self): field = fields.Enum(HairColorEnum, by_value=fields.String) assert field.deserialize("black hair") == HairColorEnum.black field = fields.Enum(GenderEnum, by_value=fields.Integer) assert field.deserialize(1) == GenderEnum.male field = fields.Enum(DateEnum, by_value=fields.Date(format="%d/%m/%Y")) assert field.deserialize("29/02/2004") == DateEnum.date_1 def test_enum_field_by_value_true_invalid_value(self): field = fields.Enum(HairColorEnum, by_value=True) with pytest.raises( ValidationError, match="Must be one of: black hair, brown hair, blond hair, red hair.", ): field.deserialize("dummy") field = fields.Enum(GenderEnum, by_value=True) with pytest.raises(ValidationError, match="Must be one of: 1, 2, 3."): field.deserialize(12) def test_enum_field_by_value_field_invalid_value(self): field = fields.Enum(HairColorEnum, by_value=fields.String) with pytest.raises( ValidationError, match="Must be one of: black hair, brown hair, blond hair, red hair.", ): field.deserialize("dummy") field = fields.Enum(GenderEnum, by_value=fields.Integer) with pytest.raises(ValidationError, match="Must be one of: 1, 2, 3."): field.deserialize(12) field = fields.Enum(DateEnum, by_value=fields.Date(format="%d/%m/%Y")) with pytest.raises( ValidationError, match="Must be one of: 29/02/2004, 29/02/2008, 29/02/2012." ): field.deserialize("28/02/2004") def test_enum_field_by_value_true_wrong_type(self): field = fields.Enum(HairColorEnum, by_value=True) with pytest.raises( ValidationError, match="Must be one of: black hair, brown hair, blond hair, red hair.", ): field.deserialize("dummy") field = fields.Enum(GenderEnum, by_value=True) with pytest.raises(ValidationError, match="Must be one of: 1, 2, 3."): field.deserialize(12) def test_enum_field_by_value_field_wrong_type(self): field = fields.Enum(HairColorEnum, by_value=fields.String) with pytest.raises(ValidationError, match="Not a valid string."): field.deserialize(12) field = fields.Enum(GenderEnum, by_value=fields.Integer) with pytest.raises(ValidationError, match="Not a valid integer."): field.deserialize("dummy") field = fields.Enum(DateEnum, by_value=fields.Date(format="%d/%m/%Y")) with pytest.raises(ValidationError, match="Not a valid date."): field.deserialize("30/02/2004") def test_deserialization_function_must_be_callable(self): with pytest.raises(TypeError): fields.Function(lambda x: None, deserialize="notvalid") def test_method_field_deserialization_is_noop_by_default(self): class MiniUserSchema(Schema): uppername = fields.Method("uppercase_name") def uppercase_name(self, obj): return obj.upper() s = MiniUserSchema() assert s.fields["uppername"].deserialize("steve") == "steve" def test_deserialization_method(self): class MiniUserSchema(Schema): uppername = fields.Method("uppercase_name", deserialize="lowercase_name") def uppercase_name(self, obj): return obj.name.upper() def lowercase_name(self, value): return value.lower() s = MiniUserSchema() assert s.fields["uppername"].deserialize("STEVE") == "steve" def test_deserialization_method_must_be_a_method(self): class BadSchema(Schema): uppername = fields.Method("uppercase_name", deserialize="lowercase_name") with pytest.raises(AttributeError): BadSchema() def test_method_field_deserialize_only(self): class MethodDeserializeOnly(Schema): name = fields.Method(deserialize="lowercase_name") def lowercase_name(self, value): return value.lower() assert MethodDeserializeOnly().load({"name": "ALEC"})["name"] == "alec" def test_datetime_list_field_deserialization(self): dtimes = dt.datetime.now(), dt.datetime.now(), dt.datetime.now(dt.timezone.utc) dstrings = [each.isoformat() for each in dtimes] field = fields.List(fields.DateTime()) result = field.deserialize(dstrings) assert all(isinstance(each, dt.datetime) for each in result) for actual, expected in zip(result, dtimes): assert_date_equal(actual, expected) def test_list_field_deserialize_invalid_item(self): field = fields.List(fields.DateTime) with pytest.raises(ValidationError) as excinfo: field.deserialize(["badvalue"]) assert excinfo.value.args[0] == {0: ["Not a valid datetime."]} field = fields.List(fields.Str()) with pytest.raises(ValidationError) as excinfo: field.deserialize(["good", 42]) assert excinfo.value.args[0] == {1: ["Not a valid string."]} def test_list_field_deserialize_multiple_invalid_items(self): field = fields.List( fields.Int( validate=validate.Range(10, 20, error="Value {input} not in range") ) ) with pytest.raises(ValidationError) as excinfo: field.deserialize([10, 5, 25]) assert len(excinfo.value.args[0]) == 2 assert excinfo.value.args[0][1] == ["Value 5 not in range"] assert excinfo.value.args[0][2] == ["Value 25 not in range"] @pytest.mark.parametrize("value", ["notalist", 42, {}]) def test_list_field_deserialize_value_that_is_not_a_list(self, value): field = fields.List(fields.Str()) with pytest.raises(ValidationError) as excinfo: field.deserialize(value) assert excinfo.value.args[0] == "Not a valid list." def test_datetime_int_tuple_field_deserialization(self): dtime = dt.datetime.now() data = dtime.isoformat(), 42 field = fields.Tuple([fields.DateTime(), fields.Integer()]) result = field.deserialize(data) assert isinstance(result, tuple) assert len(result) == 2 for val, type_, true_val in zip(result, (dt.datetime, int), (dtime, 42)): assert isinstance(val, type_) assert val == true_val def test_tuple_field_deserialize_invalid_item(self): field = fields.Tuple([fields.DateTime]) with pytest.raises(ValidationError) as excinfo: field.deserialize(["badvalue"]) assert excinfo.value.args[0] == {0: ["Not a valid datetime."]} field = fields.Tuple([fields.Str(), fields.Integer()]) with pytest.raises(ValidationError) as excinfo: field.deserialize(["good", "bad"]) assert excinfo.value.args[0] == {1: ["Not a valid integer."]} def test_tuple_field_deserialize_multiple_invalid_items(self): validator = validate.Range(10, 20, error="Value {input} not in range") field = fields.Tuple( [ fields.Int(validate=validator), fields.Int(validate=validator), fields.Int(validate=validator), ] ) with pytest.raises(ValidationError) as excinfo: field.deserialize([10, 5, 25]) assert len(excinfo.value.args[0]) == 2 assert excinfo.value.args[0][1] == ["Value 5 not in range"] assert excinfo.value.args[0][2] == ["Value 25 not in range"] @pytest.mark.parametrize("value", ["notalist", 42, {}]) def test_tuple_field_deserialize_value_that_is_not_a_collection(self, value): field = fields.Tuple([fields.Str()]) with pytest.raises(ValidationError) as excinfo: field.deserialize(value) assert excinfo.value.args[0] == "Not a valid tuple." def test_tuple_field_deserialize_invalid_length(self): field = fields.Tuple([fields.Str(), fields.Str()]) with pytest.raises(ValidationError) as excinfo: field.deserialize([42]) assert excinfo.value.args[0] == "Length must be 2." def test_constant_field_deserialization(self): field = fields.Constant("something") assert field.deserialize("whatever") == "something" def test_constant_is_always_included_in_deserialized_data(self): class MySchema(Schema): foo = fields.Constant(42) sch = MySchema() assert sch.load({})["foo"] == 42 assert sch.load({"foo": 24})["foo"] == 42 def test_field_deserialization_with_user_validator_function(self): field = fields.String(validate=lambda s: s.lower() == "valid") assert field.deserialize("Valid") == "Valid" with pytest.raises(ValidationError) as excinfo: field.deserialize("invalid") assert excinfo.value.args[0][0] == "Invalid value." assert type(excinfo.value) is ValidationError def test_field_deserialization_with_user_validator_class_that_returns_bool(self): class MyValidator: def __call__(self, val): return val == "valid" field = fields.Raw(validate=MyValidator()) assert field.deserialize("valid") == "valid" with pytest.raises(ValidationError, match="Invalid value."): field.deserialize("invalid") def test_field_deserialization_with_user_validator_that_raises_error_with_list( self, ): def validator(val): raise ValidationError(["err1", "err2"]) class MySchema(Schema): foo = fields.Raw(validate=validator) errors = MySchema().validate({"foo": 42}) assert errors["foo"] == ["err1", "err2"] def test_validator_must_return_false_to_raise_error(self): # validator returns None, so anything validates field = fields.String(validate=lambda s: None) assert field.deserialize("Valid") == "Valid" # validator returns False, so nothing validates field2 = fields.String(validate=lambda s: False) with pytest.raises(ValidationError): field2.deserialize("invalid") def test_field_deserialization_with_validator_with_nonascii_input(self): field = fields.String(validate=lambda s: False) with pytest.raises(ValidationError) as excinfo: field.deserialize("привет") assert type(excinfo.value) is ValidationError def test_field_deserialization_with_user_validators(self): validators_gen = ( func for func in ( lambda s: s.lower() == "valid", lambda s: s.lower()[::-1] == "dilav", ) ) m_colletion_type = [ fields.String( validate=[ lambda s: s.lower() == "valid", lambda s: s.lower()[::-1] == "dilav", ] ), fields.String( validate=( lambda s: s.lower() == "valid", lambda s: s.lower()[::-1] == "dilav", ) ), fields.String(validate=validators_gen), ] for field in m_colletion_type: assert field.deserialize("Valid") == "Valid" with pytest.raises(ValidationError, match="Invalid value."): field.deserialize("invalid") def test_field_deserialization_with_custom_error_message(self): field = fields.String( validate=lambda s: s.lower() == "valid", error_messages={"validator_failed": "Bad value."}, ) with pytest.raises(ValidationError, match="Bad value."): field.deserialize("invalid") # No custom deserialization behavior, so a dict is returned class SimpleUserSchema(Schema): name = fields.String() age = fields.Float() class Validator(Schema): email = fields.Email() colors = fields.Str(validate=validate.OneOf(["red", "blue"])) age = fields.Integer(validate=lambda n: n > 0) class Validators(Schema): email = fields.Email() colors = fields.Str(validate=validate.OneOf(["red", "blue"])) age = fields.Integer(validate=[lambda n: n > 0, lambda n: n < 100]) class TestSchemaDeserialization: def test_deserialize_to_dict(self): user_dict = {"name": "Monty", "age": "42.3"} result = SimpleUserSchema().load(user_dict) assert result["name"] == "Monty" assert math.isclose(result["age"], 42.3) def test_deserialize_with_missing_values(self): user_dict = {"name": "Monty"} result = SimpleUserSchema().load(user_dict) # 'age' is not included in result assert result == {"name": "Monty"} def test_deserialize_many(self): users_data = [{"name": "Mick", "age": "914"}, {"name": "Keith", "age": "8442"}] result = SimpleUserSchema(many=True).load(users_data) assert isinstance(result, list) user = result[0] assert user["age"] == int(users_data[0]["age"]) def test_exclude(self): schema = SimpleUserSchema(exclude=("age",), unknown=EXCLUDE) result = schema.load({"name": "Monty", "age": 42}) assert "name" in result assert "age" not in result def test_nested_single_deserialization_to_dict(self): class SimpleBlogSerializer(Schema): title = fields.String() author = fields.Nested(SimpleUserSchema, unknown=EXCLUDE) blog_dict = { "title": "Gimme Shelter", "author": {"name": "Mick", "age": "914", "email": "mick@stones.com"}, } result = SimpleBlogSerializer().load(blog_dict) author = result["author"] assert author["name"] == "Mick" assert author["age"] == 914 assert "email" not in author def test_nested_list_deserialization_to_dict(self): class SimpleBlogSerializer(Schema): title = fields.String() authors = fields.Nested(SimpleUserSchema, many=True) blog_dict = { "title": "Gimme Shelter", "authors": [ {"name": "Mick", "age": "914"}, {"name": "Keith", "age": "8442"}, ], } result = SimpleBlogSerializer().load(blog_dict) assert isinstance(result["authors"], list) author = result["authors"][0] assert author["name"] == "Mick" assert author["age"] == 914 def test_nested_single_none_not_allowed(self): class PetSchema(Schema): name = fields.Str() class OwnerSchema(Schema): pet = fields.Nested(PetSchema(), allow_none=False) sch = OwnerSchema() errors = sch.validate({"pet": None}) assert "pet" in errors assert errors["pet"] == ["Field may not be null."] def test_nested_many_non_not_allowed(self): class PetSchema(Schema): name = fields.Str() class StoreSchema(Schema): pets = fields.Nested(PetSchema, allow_none=False, many=True) sch = StoreSchema() errors = sch.validate({"pets": None}) assert "pets" in errors assert errors["pets"] == ["Field may not be null."] def test_nested_single_required_missing(self): class PetSchema(Schema): name = fields.Str() class OwnerSchema(Schema): pet = fields.Nested(PetSchema(), required=True) sch = OwnerSchema() errors = sch.validate({}) assert "pet" in errors assert errors["pet"] == ["Missing data for required field."] def test_nested_many_required_missing(self): class PetSchema(Schema): name = fields.Str() class StoreSchema(Schema): pets = fields.Nested(PetSchema, required=True, many=True) sch = StoreSchema() errors = sch.validate({}) assert "pets" in errors assert errors["pets"] == ["Missing data for required field."] def test_nested_only_basestring(self): class ANestedSchema(Schema): pk = fields.Str() class MainSchema(Schema): pk = fields.Str() child = fields.Pluck(ANestedSchema, "pk") sch = MainSchema() result = sch.load({"pk": "123", "child": "456"}) assert result["child"]["pk"] == "456" def test_nested_only_basestring_with_list_data(self): class ANestedSchema(Schema): pk = fields.Str() class MainSchema(Schema): pk = fields.Str() children = fields.Pluck(ANestedSchema, "pk", many=True) sch = MainSchema() result = sch.load({"pk": "123", "children": ["456", "789"]}) assert result["children"][0]["pk"] == "456" assert result["children"][1]["pk"] == "789" def test_nested_none_deserialization(self): class SimpleBlogSerializer(Schema): title = fields.String() author = fields.Nested(SimpleUserSchema, allow_none=True) blog_dict = {"title": "Gimme Shelter", "author": None} result = SimpleBlogSerializer().load(blog_dict) assert result["author"] is None assert result["title"] == blog_dict["title"] def test_deserialize_with_attribute_param(self): class AliasingUserSerializer(Schema): username = fields.Email(attribute="email") years = fields.Integer(attribute="age") data = {"username": "foo@bar.com", "years": "42"} result = AliasingUserSerializer().load(data) assert result["email"] == "foo@bar.com" assert result["age"] == 42 # regression test for https://github.com/marshmallow-code/marshmallow/issues/450 def test_deserialize_with_attribute_param_symmetry(self): class MySchema(Schema): foo = fields.Raw(attribute="bar.baz") schema = MySchema() dump_data = schema.dump({"bar": {"baz": 42}}) assert dump_data == {"foo": 42} load_data = schema.load({"foo": 42}) assert load_data == {"bar": {"baz": 42}} def test_deserialize_with_attribute_param_error_returns_field_name_not_attribute_name( self, ): class AliasingUserSerializer(Schema): username = fields.Email(attribute="email") years = fields.Integer(attribute="age") data = {"username": "foobar.com", "years": "42"} with pytest.raises(ValidationError) as excinfo: AliasingUserSerializer().load(data) errors = excinfo.value.messages assert errors["username"] == ["Not a valid email address."] def test_deserialize_with_attribute_param_error_returns_data_key_not_attribute_name( self, ): class AliasingUserSerializer(Schema): name = fields.String(data_key="Name") username = fields.Email(attribute="email", data_key="UserName") years = fields.Integer(attribute="age", data_key="Years") data = {"Name": "Mick", "UserName": "foobar.com", "Years": "abc"} with pytest.raises(ValidationError) as excinfo: AliasingUserSerializer().load(data) errors = excinfo.value.messages assert errors["UserName"] == ["Not a valid email address."] assert errors["Years"] == ["Not a valid integer."] def test_deserialize_with_data_key_param(self): class AliasingUserSerializer(Schema): name = fields.String(data_key="Name") username = fields.Email(attribute="email", data_key="UserName") years = fields.Integer(data_key="Years") data = {"Name": "Mick", "UserName": "foo@bar.com", "years": "42"} result = AliasingUserSerializer(unknown=EXCLUDE).load(data) assert result["name"] == "Mick" assert result["email"] == "foo@bar.com" assert "years" not in result def test_deserialize_with_data_key_as_empty_string(self): class MySchema(Schema): name = fields.Raw(data_key="") schema = MySchema() assert schema.load({"": "Grace"}) == {"name": "Grace"} def test_deserialize_with_dump_only_param(self): class AliasingUserSerializer(Schema): name = fields.String() years = fields.Integer(dump_only=True) size = fields.Integer(dump_only=True, load_only=True) nicknames = fields.List(fields.Str(), dump_only=True) data = { "name": "Mick", "years": "42", "size": "12", "nicknames": ["Your Majesty", "Brenda"], } result = AliasingUserSerializer(unknown=EXCLUDE).load(data) assert result["name"] == "Mick" assert "years" not in result assert "size" not in result assert "nicknames" not in result def test_deserialize_with_missing_param_value(self): bdate = dt.datetime(2017, 9, 29) class AliasingUserSerializer(Schema): name = fields.String() birthdate = fields.DateTime(load_default=bdate) data = {"name": "Mick"} result = AliasingUserSerializer().load(data) assert result["name"] == "Mick" assert result["birthdate"] == bdate def test_deserialize_with_missing_param_callable(self): bdate = dt.datetime(2017, 9, 29) class AliasingUserSerializer(Schema): name = fields.String() birthdate = fields.DateTime(load_default=lambda: bdate) data = {"name": "Mick"} result = AliasingUserSerializer().load(data) assert result["name"] == "Mick" assert result["birthdate"] == bdate def test_deserialize_with_missing_param_none(self): class AliasingUserSerializer(Schema): name = fields.String() years = fields.Integer(load_default=None, allow_none=True) data = {"name": "Mick"} result = AliasingUserSerializer().load(data) assert result["name"] == "Mick" assert result["years"] is None def test_deserialization_raises_with_errors(self): bad_data = {"email": "invalid-email", "colors": "burger", "age": -1} v = Validator() with pytest.raises(ValidationError) as excinfo: v.load(bad_data) errors = excinfo.value.messages assert "email" in errors assert "colors" in errors assert "age" in errors def test_deserialization_raises_with_errors_with_multiple_validators(self): bad_data = {"email": "invalid-email", "colors": "burger", "age": -1} v = Validators() with pytest.raises(ValidationError) as excinfo: v.load(bad_data) errors = excinfo.value.messages assert "email" in errors assert "colors" in errors assert "age" in errors def test_deserialization_many_raises_errors(self): bad_data = [ {"email": "foo@bar.com", "colors": "red", "age": 18}, {"email": "bad", "colors": "pizza", "age": -1}, ] v = Validator(many=True) with pytest.raises(ValidationError): v.load(bad_data) def test_validation_errors_are_stored(self): def validate_field(val): raise ValidationError("Something went wrong") class MySchema(Schema): foo = fields.Raw(validate=validate_field) with pytest.raises(ValidationError) as excinfo: MySchema().load({"foo": 42}) errors = excinfo.value.messages assert "Something went wrong" in errors["foo"] def test_multiple_errors_can_be_stored_for_a_field(self): def validate_with_bool(n): return False def validate_with_error(n): raise ValidationError("foo is not valid") class MySchema(Schema): foo = fields.Raw( required=True, validate=[validate_with_bool, validate_with_error] ) with pytest.raises(ValidationError) as excinfo: MySchema().load({"foo": "bar"}) errors = excinfo.value.messages assert type(errors["foo"]) is list assert len(errors["foo"]) == 2 def test_multiple_errors_can_be_stored_for_an_email_field(self): def validate_with_bool(val): return False class MySchema(Schema): email = fields.Email(validate=[validate_with_bool]) with pytest.raises(ValidationError) as excinfo: MySchema().load({"email": "foo"}) errors = excinfo.value.messages assert len(errors["email"]) == 2 assert "Not a valid email address." in errors["email"][0] def test_multiple_errors_can_be_stored_for_a_url_field(self): def validate_with_bool(val): return False class MySchema(Schema): url = fields.Url(validate=[validate_with_bool]) with pytest.raises(ValidationError) as excinfo: MySchema().load({"url": "foo"}) errors = excinfo.value.messages assert len(errors["url"]) == 2 assert "Not a valid URL." in errors["url"][0] def test_required_value_only_passed_to_validators_if_provided(self): class MySchema(Schema): foo = fields.Raw(required=True, validate=lambda f: False) with pytest.raises(ValidationError) as excinfo: MySchema().load({}) errors = excinfo.value.messages # required value missing assert len(errors["foo"]) == 1 assert "Missing data for required field." in errors["foo"] @pytest.mark.parametrize("partial_schema", [True, False]) def test_partial_deserialization(self, partial_schema): class MySchema(Schema): foo = fields.Raw(required=True) bar = fields.Raw(required=True) schema_args = {} load_args = {} if partial_schema: schema_args["partial"] = True else: load_args["partial"] = True data = MySchema(**schema_args).load({"foo": 3}, **load_args) assert data["foo"] == 3 assert "bar" not in data def test_partial_fields_deserialization(self): class MySchema(Schema): foo = fields.Raw(required=True) bar = fields.Raw(required=True) baz = fields.Raw(required=True) with pytest.raises(ValidationError) as excinfo: MySchema().load({"foo": 3}, partial=tuple()) data, errors = excinfo.value.valid_data, excinfo.value.messages assert data["foo"] == 3 assert "bar" in errors assert "baz" in errors data = MySchema().load({"foo": 3}, partial=("bar", "baz")) assert data["foo"] == 3 assert "bar" not in data assert "baz" not in data data = MySchema(partial=True).load({"foo": 3}, partial=("bar", "baz")) assert data["foo"] == 3 assert "bar" not in data assert "baz" not in data def test_partial_fields_validation(self): class MySchema(Schema): foo = fields.Raw(required=True) bar = fields.Raw(required=True) baz = fields.Raw(required=True) errors = MySchema().validate({"foo": 3}, partial=tuple()) assert "bar" in errors assert "baz" in errors errors = MySchema().validate({"foo": 3}, partial=("bar", "baz")) assert errors == {} errors = MySchema(partial=True).validate({"foo": 3}, partial=("bar", "baz")) assert errors == {} def test_unknown_fields_deserialization(self): class MySchema(Schema): foo = fields.Integer() data = MySchema(unknown=EXCLUDE).load({"foo": 3, "bar": 5}) assert data["foo"] == 3 assert "bar" not in data data = MySchema(unknown=INCLUDE).load({"foo": 3, "bar": 5}, unknown=EXCLUDE) assert data["foo"] == 3 assert "bar" not in data data = MySchema(unknown=EXCLUDE).load({"foo": 3, "bar": 5}, unknown=INCLUDE) assert data["foo"] == 3 assert data["bar"] data = MySchema(unknown=INCLUDE).load({"foo": 3, "bar": 5}) assert data["foo"] == 3 assert data["bar"] with pytest.raises(ValidationError, match="foo"): MySchema(unknown=INCLUDE).load({"foo": "asd", "bar": 5}) data = MySchema(unknown=INCLUDE, many=True).load( [{"foo": 1}, {"foo": 3, "bar": 5}] ) assert "foo" in data[1] assert "bar" in data[1] with pytest.raises(ValidationError) as excinfo: MySchema().load({"foo": 3, "bar": 5}) err = excinfo.value assert "bar" in err.messages assert err.messages["bar"] == ["Unknown field."] with pytest.raises(ValidationError) as excinfo: MySchema(many=True).load([{"foo": "abc"}, {"foo": 3, "bar": 5}]) err = excinfo.value assert 0 in err.messages assert "foo" in err.messages[0] assert err.messages[0]["foo"] == ["Not a valid integer."] assert 1 in err.messages assert "bar" in err.messages[1] assert err.messages[1]["bar"] == ["Unknown field."] def test_unknown_fields_deserialization_precedence(self): class MySchema(Schema): class Meta: unknown = INCLUDE foo = fields.Integer() data = MySchema().load({"foo": 3, "bar": 5}) assert data["foo"] == 3 assert data["bar"] == 5 data = MySchema(unknown=EXCLUDE).load({"foo": 3, "bar": 5}) assert data["foo"] == 3 assert "bar" not in data data = MySchema().load({"foo": 3, "bar": 5}, unknown=EXCLUDE) assert data["foo"] == 3 assert "bar" not in data with pytest.raises(ValidationError): MySchema(unknown=EXCLUDE).load({"foo": 3, "bar": 5}, unknown=RAISE) def test_unknown_fields_deserialization_with_data_key(self): class MySchema(Schema): foo = fields.Integer(data_key="Foo") data = MySchema().load({"Foo": 1}) assert data["foo"] == 1 assert "Foo" not in data data = MySchema(unknown=RAISE).load({"Foo": 1}) assert data["foo"] == 1 assert "Foo" not in data with pytest.raises(ValidationError): MySchema(unknown=RAISE).load({"foo": 1}) data = MySchema(unknown=INCLUDE).load({"Foo": 1}) assert data["foo"] == 1 assert "Foo" not in data def test_unknown_fields_deserialization_with_index_errors_false(self): class MySchema(Schema): foo = fields.Integer() class Meta: unknown = RAISE index_errors = False with pytest.raises(ValidationError) as excinfo: MySchema(many=True).load([{"foo": "invalid"}, {"foo": 42, "bar": 24}]) err = excinfo.value assert 1 not in err.messages assert "foo" in err.messages assert "bar" in err.messages assert err.messages["foo"] == ["Not a valid integer."] assert err.messages["bar"] == ["Unknown field."] def test_dump_only_fields_considered_unknown(self): class MySchema(Schema): foo = fields.Int(dump_only=True) with pytest.raises(ValidationError) as excinfo: MySchema().load({"foo": 42}) err = excinfo.value assert "foo" in err.messages assert err.messages["foo"] == ["Unknown field."] # When unknown = INCLUDE, dump-only fields are included as unknown # without any validation. data = MySchema(unknown=INCLUDE).load({"foo": "LOL"}) assert data["foo"] == "LOL" def test_unknown_fields_do_not_unpack_dotted_names(self): class MySchema(Schema): class Meta: unknown = INCLUDE foo = fields.Str() bar = fields.Str(data_key="bar.baz") # dotted names are still supported data = MySchema().load({"foo": "hi", "bar.baz": "okay"}) assert data == {"foo": "hi", "bar": "okay"} # but extra keys included via unknown=INCLUDE are not transformed into nested dicts data = MySchema().load({"foo": "hi", "bar.baz": "okay", "alpha.beta": "woah!"}) assert data == {"foo": "hi", "bar": "okay", "alpha.beta": "woah!"} validators_gen = (func for func in [lambda x: x <= 24, lambda x: x >= 18]) validators_gen_float = (func for func in [lambda f: f <= 4.1, lambda f: f >= 1.0]) validators_gen_str = ( func for func in [lambda n: len(n) == 3, lambda n: n[1].lower() == "o"] ) class TestValidation: def test_integer_with_validator(self): field = fields.Integer(validate=lambda x: 18 <= x <= 24) out = field.deserialize("20") assert out == 20 with pytest.raises(ValidationError): field.deserialize(25) @pytest.mark.parametrize( "field", [ fields.Integer(validate=[lambda x: x <= 24, lambda x: x >= 18]), fields.Integer(validate=(lambda x: x <= 24, lambda x: x >= 18)), fields.Integer(validate=validators_gen), ], ) def test_integer_with_validators(self, field): out = field.deserialize("20") assert out == 20 with pytest.raises(ValidationError): field.deserialize(25) @pytest.mark.parametrize( "field", [ fields.Float(validate=[lambda f: f <= 4.1, lambda f: f >= 1.0]), fields.Float(validate=(lambda f: f <= 4.1, lambda f: f >= 1.0)), fields.Float(validate=validators_gen_float), ], ) def test_float_with_validators(self, field): assert field.deserialize(3.14) with pytest.raises(ValidationError): field.deserialize(4.2) def test_string_validator(self): field = fields.String(validate=lambda n: len(n) == 3) assert field.deserialize("Joe") == "Joe" with pytest.raises(ValidationError): field.deserialize("joseph") def test_function_validator(self): field = fields.Function( lambda d: d.name.upper(), validate=lambda n: len(n) == 3 ) assert field.deserialize("joe") with pytest.raises(ValidationError): field.deserialize("joseph") @pytest.mark.parametrize( "field", [ fields.Function( lambda d: d.name.upper(), validate=[lambda n: len(n) == 3, lambda n: n[1].lower() == "o"], ), fields.Function( lambda d: d.name.upper(), validate=(lambda n: len(n) == 3, lambda n: n[1].lower() == "o"), ), fields.Function(lambda d: d.name.upper(), validate=validators_gen_str), ], ) def test_function_validators(self, field): assert field.deserialize("joe") with pytest.raises(ValidationError): field.deserialize("joseph") def test_method_validator(self): class MethodSerializer(Schema): name = fields.Method( "get_name", deserialize="get_name", validate=lambda n: len(n) == 3 ) def get_name(self, val): return val.upper() assert MethodSerializer().load({"name": "joe"}) with pytest.raises(ValidationError, match="Invalid value."): MethodSerializer().load({"name": "joseph"}) # Regression test for https://github.com/marshmallow-code/marshmallow/issues/269 def test_nested_data_is_stored_when_validation_fails(self): class SchemaA(Schema): x = fields.Integer() y = fields.Integer(validate=lambda n: n > 0) z = fields.Integer() class SchemaB(Schema): w = fields.Integer() n = fields.Nested(SchemaA) sch = SchemaB() with pytest.raises(ValidationError) as excinfo: sch.load({"w": 90, "n": {"x": 90, "y": 89, "z": None}}) data, errors = excinfo.value.valid_data, excinfo.value.messages assert "z" in errors["n"] assert data == {"w": 90, "n": {"x": 90, "y": 89}} with pytest.raises(ValidationError) as excinfo: sch.load({"w": 90, "n": {"x": 90, "y": -1, "z": 180}}) data, errors = excinfo.value.valid_data, excinfo.value.messages assert "y" in errors["n"] assert data == {"w": 90, "n": {"x": 90, "z": 180}} def test_false_value_validation(self): class Sch(Schema): lamb = fields.Raw(validate=lambda x: x is False) equal = fields.Raw(validate=Equal(False)) errors = Sch().validate({"lamb": False, "equal": False}) assert not errors errors = Sch().validate({"lamb": True, "equal": True}) assert "lamb" in errors assert errors["lamb"] == ["Invalid value."] assert "equal" in errors assert errors["equal"] == ["Must be equal to False."] def test_nested_partial_load(self): class SchemaA(Schema): x = fields.Integer(required=True) y = fields.Integer() class SchemaB(Schema): z = fields.Nested(SchemaA) b_dict = {"z": {"y": 42}} # Partial loading shouldn't generate any errors. result = SchemaB().load(b_dict, partial=True) assert result["z"]["y"] == 42 # Non partial loading should complain about missing values. with pytest.raises(ValidationError) as excinfo: SchemaB().load(b_dict) data, errors = excinfo.value.valid_data, excinfo.value.messages assert data["z"]["y"] == 42 assert "z" in errors assert "x" in errors["z"] def test_deeply_nested_partial_load(self): class SchemaC(Schema): x = fields.Integer(required=True) y = fields.Integer() class SchemaB(Schema): c = fields.Nested(SchemaC) class SchemaA(Schema): b = fields.Nested(SchemaB) a_dict = {"b": {"c": {"y": 42}}} # Partial loading shouldn't generate any errors. result = SchemaA().load(a_dict, partial=True) assert result["b"]["c"]["y"] == 42 # Non partial loading should complain about missing values. with pytest.raises(ValidationError) as excinfo: SchemaA().load(a_dict) data, errors = excinfo.value.valid_data, excinfo.value.messages assert data["b"]["c"]["y"] == 42 assert "b" in errors assert "c" in errors["b"] assert "x" in errors["b"]["c"] def test_nested_partial_tuple(self): class SchemaA(Schema): x = fields.Integer(required=True) y = fields.Integer(required=True) class SchemaB(Schema): z = fields.Nested(SchemaA) b_dict = {"z": {"y": 42}} # If we ignore the missing z.x, z.y should still load. result = SchemaB().load(b_dict, partial=("z.x",)) assert result["z"]["y"] == 42 # If we ignore a missing z.y we should get a validation error. with pytest.raises(ValidationError): SchemaB().load(b_dict, partial=("z.y",)) def test_nested_partial_default(self): class SchemaA(Schema): x = fields.Integer(required=True) y = fields.Integer(required=True) class SchemaB(Schema): z = fields.Nested(SchemaA(partial=("x",))) b_dict = {"z": {"y": 42}} # Nested partial args should be respected. result = SchemaB().load(b_dict) assert result["z"]["y"] == 42 with pytest.raises(ValidationError): SchemaB().load({"z": {"x": 0}}) @pytest.mark.parametrize("FieldClass", ALL_FIELDS) def test_required_field_failure(FieldClass): class RequireSchema(Schema): age = FieldClass(required=True) user_data = {"name": "Phil"} with pytest.raises(ValidationError) as excinfo: RequireSchema().load(user_data) errors = excinfo.value.messages assert "Missing data for required field." in errors["age"] @pytest.mark.parametrize( "message", [ "My custom required message", {"error": "something", "code": 400}, ["first error", "second error"], ], ) def test_required_message_can_be_changed(message): class RequireSchema(Schema): age = fields.Integer(required=True, error_messages={"required": message}) user_data = {"name": "Phil"} with pytest.raises(ValidationError) as excinfo: RequireSchema().load(user_data) errors = excinfo.value.messages expected = [message] if isinstance(message, str) else message assert expected == errors["age"] @pytest.mark.parametrize("unknown", (EXCLUDE, INCLUDE, RAISE)) @pytest.mark.parametrize("data", [True, False, 42, None, []]) def test_deserialize_raises_exception_if_input_type_is_incorrect(data, unknown): class MySchema(Schema): foo = fields.Raw() bar = fields.Raw() with pytest.raises(ValidationError, match="Invalid input type.") as excinfo: MySchema(unknown=unknown).load(data) exc = excinfo.value assert list(exc.messages.keys()) == ["_schema"] marshmallow-3.26.1/tests/test_error_store.py000066400000000000000000000121331475016050200212510ustar00rootroot00000000000000from typing import NamedTuple from marshmallow import missing from marshmallow.error_store import merge_errors def test_missing_is_falsy(): assert bool(missing) is False class CustomError(NamedTuple): code: int message: str class TestMergeErrors: def test_merging_none_and_string(self): assert merge_errors(None, "error1") == "error1" def test_merging_none_and_custom_error(self): assert CustomError(123, "error1") == merge_errors( None, CustomError(123, "error1") ) def test_merging_none_and_list(self): assert merge_errors(None, ["error1", "error2"]) == ["error1", "error2"] def test_merging_none_and_dict(self): assert merge_errors(None, {"field1": "error1"}) == {"field1": "error1"} def test_merging_string_and_none(self): assert merge_errors("error1", None) == "error1" def test_merging_custom_error_and_none(self): assert CustomError(123, "error1") == merge_errors( CustomError(123, "error1"), None ) def test_merging_list_and_none(self): assert merge_errors(["error1", "error2"], None) == ["error1", "error2"] def test_merging_dict_and_none(self): assert merge_errors({"field1": "error1"}, None) == {"field1": "error1"} def test_merging_string_and_string(self): assert merge_errors("error1", "error2") == ["error1", "error2"] def test_merging_custom_error_and_string(self): assert [CustomError(123, "error1"), "error2"] == merge_errors( CustomError(123, "error1"), "error2" ) def test_merging_string_and_custom_error(self): assert ["error1", CustomError(123, "error2")] == merge_errors( "error1", CustomError(123, "error2") ) def test_merging_custom_error_and_custom_error(self): assert [CustomError(123, "error1"), CustomError(456, "error2")] == merge_errors( CustomError(123, "error1"), CustomError(456, "error2") ) def test_merging_string_and_list(self): assert merge_errors("error1", ["error2"]) == ["error1", "error2"] def test_merging_string_and_dict(self): assert merge_errors("error1", {"field1": "error2"}) == { "_schema": "error1", "field1": "error2", } def test_merging_string_and_dict_with_schema_error(self): assert merge_errors("error1", {"_schema": "error2", "field1": "error3"}) == { "_schema": ["error1", "error2"], "field1": "error3", } def test_merging_custom_error_and_list(self): assert [CustomError(123, "error1"), "error2"] == merge_errors( CustomError(123, "error1"), ["error2"] ) def test_merging_custom_error_and_dict(self): assert { "_schema": CustomError(123, "error1"), "field1": "error2", } == merge_errors(CustomError(123, "error1"), {"field1": "error2"}) def test_merging_custom_error_and_dict_with_schema_error(self): assert { "_schema": [CustomError(123, "error1"), "error2"], "field1": "error3", } == merge_errors( CustomError(123, "error1"), {"_schema": "error2", "field1": "error3"} ) def test_merging_list_and_string(self): assert merge_errors(["error1"], "error2") == ["error1", "error2"] def test_merging_list_and_custom_error(self): assert ["error1", CustomError(123, "error2")] == merge_errors( ["error1"], CustomError(123, "error2") ) def test_merging_list_and_list(self): assert merge_errors(["error1"], ["error2"]) == ["error1", "error2"] def test_merging_list_and_dict(self): assert merge_errors(["error1"], {"field1": "error2"}) == { "_schema": ["error1"], "field1": "error2", } def test_merging_list_and_dict_with_schema_error(self): assert merge_errors(["error1"], {"_schema": "error2", "field1": "error3"}) == { "_schema": ["error1", "error2"], "field1": "error3", } def test_merging_dict_and_string(self): assert merge_errors({"field1": "error1"}, "error2") == { "_schema": "error2", "field1": "error1", } def test_merging_dict_and_custom_error(self): assert { "_schema": CustomError(123, "error2"), "field1": "error1", } == merge_errors({"field1": "error1"}, CustomError(123, "error2")) def test_merging_dict_and_list(self): assert merge_errors({"field1": "error1"}, ["error2"]) == { "_schema": ["error2"], "field1": "error1", } def test_merging_dict_and_dict(self): assert merge_errors( {"field1": "error1", "field2": "error2"}, {"field2": "error3", "field3": "error4"}, ) == { "field1": "error1", "field2": ["error2", "error3"], "field3": "error4", } def test_deep_merging_dicts(self): assert merge_errors( {"field1": {"field2": "error1"}}, {"field1": {"field2": "error2"}} ) == {"field1": {"field2": ["error1", "error2"]}} marshmallow-3.26.1/tests/test_exceptions.py000066400000000000000000000026101475016050200210640ustar00rootroot00000000000000import pytest from marshmallow.exceptions import ValidationError class TestValidationError: def test_stores_message_in_list(self): err = ValidationError("foo") assert err.messages == ["foo"] def test_can_pass_list_of_messages(self): err = ValidationError(["foo", "bar"]) assert err.messages == ["foo", "bar"] def test_stores_dictionaries(self): messages = {"user": {"email": ["email is invalid"]}} err = ValidationError(messages) assert err.messages == messages def test_can_store_field_name(self): err = ValidationError("invalid email", field_name="email") assert err.field_name == "email" def test_str(self): err = ValidationError("invalid email") assert str(err) == "invalid email" err2 = ValidationError("invalid email", "email") assert str(err2) == "invalid email" def test_stores_dictionaries_in_messages_dict(self): messages = {"user": {"email": ["email is invalid"]}} err = ValidationError(messages) assert err.messages_dict == messages def test_messages_dict_type_error_on_badval(self): err = ValidationError("foo") with pytest.raises(TypeError) as excinfo: err.messages_dict # noqa: B018 assert "cannot access 'messages_dict' when 'messages' is of type list" in str( excinfo.value ) marshmallow-3.26.1/tests/test_fields.py000066400000000000000000000577231475016050200201700ustar00rootroot00000000000000import pytest from marshmallow import ( EXCLUDE, INCLUDE, RAISE, Schema, ValidationError, fields, missing, ) from marshmallow.exceptions import StringNotCollectionError from marshmallow.orderedset import OrderedSet from tests.base import ALL_FIELDS @pytest.mark.parametrize( ("alias", "field"), [ (fields.Int, fields.Integer), (fields.Str, fields.String), (fields.Bool, fields.Boolean), (fields.URL, fields.Url), ], ) def test_field_aliases(alias, field): assert alias is field class TestField: def test_repr(self): default = "œ∑´" # noqa: RUF001 field = fields.Raw(dump_default=default, attribute=None) assert repr(field) == ( f"" ) int_field = fields.Integer(validate=lambda x: True) assert " 3) bar = fields.Int(validate=lambda x: x > 3) sch = MySchema() valid = {"foo": "loll", "bar": 42} invalid = {"foo": "lol", "bar": 3} errors = sch.validate([valid, invalid], many=True) assert 1 in errors assert len(errors[1]) == 2 assert "foo" in errors[1] assert "bar" in errors[1] def test_dump_returns_a_dict(user): s = UserSchema() result = s.dump(user) assert type(result) is dict def test_dumps_returns_a_string(user): s = UserSchema() result = s.dumps(user) assert type(result) is str def test_dumping_single_object_with_collection_schema(user): s = UserSchema(many=True) result = s.dump(user, many=False) assert type(result) is dict assert result == UserSchema().dump(user) def test_loading_single_object_with_collection_schema(): s = UserSchema(many=True) in_data = {"name": "Mick", "email": "mick@stones.com"} result = s.load(in_data, many=False) assert type(result) is User assert result.name == UserSchema().load(in_data).name def test_dumps_many(): s = UserSchema() u1, u2 = User("Mick"), User("Keith") json_result = s.dumps([u1, u2], many=True) data = json.loads(json_result) assert len(data) == 2 assert data[0] == s.dump(u1) def test_load_returns_an_object(): s = UserSchema() result = s.load({"name": "Monty"}) assert type(result) is User def test_load_many(): s = UserSchema() in_data = [{"name": "Mick"}, {"name": "Keith"}] result = s.load(in_data, many=True) assert type(result) is list assert type(result[0]) is User assert result[0].name == "Mick" @pytest.mark.parametrize("val", (None, False, 1, 1.2, object(), [], set(), "lol")) def test_load_invalid_input_type(val): class Sch(Schema): name = fields.Str() with pytest.raises(ValidationError) as e: Sch().load(val) assert e.value.messages == {"_schema": ["Invalid input type."]} assert e.value.valid_data == {} # regression test for https://github.com/marshmallow-code/marshmallow/issues/906 @pytest.mark.parametrize("val", (None, False, 1, 1.2, object(), {}, {"1": 2}, "lol")) def test_load_many_invalid_input_type(val): class Sch(Schema): name = fields.Str() with pytest.raises(ValidationError) as e: Sch(many=True).load(val) assert e.value.messages == {"_schema": ["Invalid input type."]} assert e.value.valid_data == [] @pytest.mark.parametrize("val", ([], set())) def test_load_many_empty_collection(val): class Sch(Schema): name = fields.Str() assert Sch(many=True).load(val) == [] @pytest.mark.parametrize("val", (False, 1, 1.2, object(), {}, {"1": 2}, "lol")) def test_load_many_in_nested_invalid_input_type(val): class Inner(Schema): name = fields.String() class Outer(Schema): list1 = fields.List(fields.Nested(Inner)) list2 = fields.Nested(Inner, many=True) with pytest.raises(ValidationError) as e: Outer().load({"list1": val, "list2": val}) # TODO: Error messages should be identical (#779) assert e.value.messages == { "list1": ["Not a valid list."], "list2": ["Invalid type."], } @pytest.mark.parametrize("val", ([], set())) def test_load_many_in_nested_empty_collection(val): class Inner(Schema): name = fields.String() class Outer(Schema): list1 = fields.List(fields.Nested(Inner)) list2 = fields.Nested(Inner, many=True) assert Outer().load({"list1": val, "list2": val}) == {"list1": [], "list2": []} def test_loads_returns_a_user(): s = UserSchema() result = s.loads(json.dumps({"name": "Monty"})) assert type(result) is User def test_loads_many(): s = UserSchema() in_data = [{"name": "Mick"}, {"name": "Keith"}] in_json_data = json.dumps(in_data) result = s.loads(in_json_data, many=True) assert type(result) is list assert result[0].name == "Mick" def test_loads_deserializes_from_json(): user_dict = {"name": "Monty", "age": "42.3"} user_json = json.dumps(user_dict) result = UserSchema().loads(user_json) assert isinstance(result, User) assert result.name == "Monty" assert math.isclose(result.age, 42.3) def test_serializing_none(): class MySchema(Schema): id = fields.Str(dump_default="no-id") num = fields.Int() name = fields.Str() data = UserSchema().dump(None) assert data == {"id": "no-id"} def test_default_many_symmetry(): """The dump/load(s) methods should all default to the many value of the schema.""" s_many = UserSchema(many=True, only=("name",)) s_single = UserSchema(only=("name",)) u1, u2 = User("King Arthur"), User("Sir Lancelot") s_single.load(s_single.dump(u1)) s_single.loads(s_single.dumps(u1)) s_many.load(s_many.dump([u1, u2])) s_many.loads(s_many.dumps([u1, u2])) def test_on_bind_field_hook(): class MySchema(Schema): foo = fields.Str() def on_bind_field(self, field_name, field_obj): assert field_obj.parent is self field_obj.metadata["fname"] = field_name schema = MySchema() assert schema.fields["foo"].metadata["fname"] == "foo" def test_nested_on_bind_field_hook(): class MySchema(Schema): class NestedSchema(Schema): bar = fields.Str() def on_bind_field(self, field_name, field_obj): field_obj.metadata["fname"] = self.context["fname"] foo = fields.Nested(NestedSchema) with pytest.warns(RemovedInMarshmallow4Warning): schema1 = MySchema(context={"fname": "foobar"}) schema2 = MySchema(context={"fname": "quxquux"}) assert schema1.fields["foo"].schema.fields["bar"].metadata["fname"] == "foobar" assert schema2.fields["foo"].schema.fields["bar"].metadata["fname"] == "quxquux" class TestValidate: def test_validate_raises_with_errors_dict(self): s = UserSchema() errors = s.validate({"email": "bad-email", "name": "Valid Name"}) assert type(errors) is dict assert "email" in errors assert "name" not in errors valid_data = {"name": "Valid Name", "email": "valid@email.com"} errors = s.validate(valid_data) assert errors == {} def test_validate_many(self): s = UserSchema(many=True) in_data = [ {"name": "Valid Name", "email": "validemail@hotmail.com"}, {"name": "Valid Name2", "email": "invalid"}, ] errors = s.validate(in_data, many=True) assert 1 in errors assert "email" in errors[1] def test_validate_many_doesnt_store_index_if_index_errors_option_is_false(self): class NoIndex(Schema): email = fields.Email() class Meta: index_errors = False s = NoIndex() in_data = [ {"name": "Valid Name", "email": "validemail@hotmail.com"}, {"name": "Valid Name2", "email": "invalid"}, ] errors = s.validate(in_data, many=True) assert 1 not in errors assert "email" in errors def test_validate(self): s = UserSchema() errors = s.validate({"email": "bad-email"}) assert errors == {"email": ["Not a valid email address."]} def test_validate_required(self): class MySchema(Schema): foo = fields.Raw(required=True) s = MySchema() errors = s.validate({"bar": 42}) assert "foo" in errors assert "required" in errors["foo"][0] @pytest.mark.parametrize("SchemaClass", [UserSchema, UserMetaSchema]) def test_fields_are_not_copies(SchemaClass): s = SchemaClass() s2 = SchemaClass() assert s.fields is not s2.fields def test_dumps_returns_json(user): ser = UserSchema() serialized = ser.dump(user) json_data = ser.dumps(user) assert type(json_data) is str expected = json.dumps(serialized) assert json_data == expected def test_naive_datetime_field(user, serialized_user): expected = utils.isoformat(user.created) assert serialized_user["created"] == expected def test_datetime_formatted_field(user, serialized_user): result = serialized_user["created_formatted"] assert result == user.created.strftime("%Y-%m-%d") def test_datetime_iso_field(user, serialized_user): assert serialized_user["created_iso"] == utils.isoformat(user.created) def test_tz_datetime_field(user, serialized_user): # Datetime is corrected back to GMT expected = utils.isoformat(user.updated) assert serialized_user["updated"] == expected def test_class_variable(serialized_user): assert serialized_user["species"] == "Homo sapiens" @pytest.mark.parametrize("SchemaClass", [UserSchema, UserMetaSchema]) def test_serialize_many(SchemaClass): user1 = User(name="Mick", age=123) user2 = User(name="Keith", age=456) users = [user1, user2] serialized = SchemaClass(many=True).dump(users) assert len(serialized) == 2 assert serialized[0]["name"] == "Mick" assert serialized[1]["name"] == "Keith" def test_inheriting_schema(user): sch = ExtendedUserSchema() result = sch.dump(user) assert result["name"] == user.name user.is_old = True result = sch.dump(user) assert result["is_old"] is True def test_custom_field(serialized_user, user): assert serialized_user["uppername"] == user.name.upper() def test_url_field(serialized_user, user): assert serialized_user["homepage"] == user.homepage def test_relative_url_field(): u = {"name": "John", "homepage": "/foo"} UserRelativeUrlSchema().load(u) @pytest.mark.parametrize("SchemaClass", [UserSchema, UserMetaSchema]) def test_stores_invalid_url_error(SchemaClass): user = {"name": "Steve", "homepage": "www.foo.com"} with pytest.raises(ValidationError) as excinfo: SchemaClass().load(user) errors = excinfo.value.messages assert "homepage" in errors expected = ["Not a valid URL."] assert errors["homepage"] == expected @pytest.mark.parametrize("SchemaClass", [UserSchema, UserMetaSchema]) def test_email_field(SchemaClass): u = User("John", email="john@example.com") s = SchemaClass().dump(u) assert s["email"] == "john@example.com" def test_stored_invalid_email(): u = {"name": "John", "email": "johnexample.com"} with pytest.raises(ValidationError) as excinfo: UserSchema().load(u) errors = excinfo.value.messages assert "email" in errors assert errors["email"][0] == "Not a valid email address." def test_integer_field(): u = User("John", age=42.3) serialized = UserIntSchema().dump(u) assert type(serialized["age"]) is int assert serialized["age"] == 42 def test_as_string(): u = User("John", age=42.3) serialized = UserFloatStringSchema().dump(u) assert type(serialized["age"]) is str assert math.isclose(float(serialized["age"]), 42.3) @pytest.mark.parametrize("SchemaClass", [UserSchema, UserMetaSchema]) def test_method_field(SchemaClass, serialized_user): assert serialized_user["is_old"] is False u = User("Joe", age=81) assert SchemaClass().dump(u)["is_old"] is True def test_function_field(serialized_user, user): assert serialized_user["lowername"] == user.name.lower() def test_fields_must_be_declared_as_instances(user): class BadUserSchema(Schema): name = fields.String with pytest.raises( TypeError, match='Field for "name" must be declared as a Field instance' ): BadUserSchema().dump(user) # regression test def test_bind_field_does_not_swallow_typeerror(): class MySchema(Schema): name = fields.Str() def on_bind_field(self, field_name, field_obj): raise TypeError("boom") with pytest.raises(TypeError, match="boom"): MySchema() @pytest.mark.parametrize("SchemaClass", [UserSchema, UserMetaSchema]) def test_serializing_generator(SchemaClass): users = [User("Foo"), User("Bar")] user_gen = (u for u in users) s = SchemaClass(many=True).dump(user_gen) assert len(s) == 2 assert s[0] == SchemaClass().dump(users[0]) def test_serializing_empty_list_returns_empty_list(): assert UserSchema(many=True).dump([]) == [] assert UserMetaSchema(many=True).dump([]) == [] def test_serializing_dict(): user = { "name": "foo", "email": "foo@bar.com", "age": 42, "various_data": {"foo": "bar"}, } data = UserSchema().dump(user) assert data["name"] == "foo" assert data["age"] == 42 assert data["various_data"] == {"foo": "bar"} def test_serializing_dict_with_meta_fields(): class MySchema(Schema): class Meta: fields = ("foo", "bar") sch = MySchema() data = sch.dump({"foo": 42, "bar": 24, "baz": 424}) assert data["foo"] == 42 assert data["bar"] == 24 assert "baz" not in data @pytest.mark.parametrize("SchemaClass", [UserSchema, UserMetaSchema]) def test_exclude_in_init(SchemaClass, user): s = SchemaClass(exclude=("age", "homepage")).dump(user) assert "homepage" not in s assert "age" not in s assert "name" in s @pytest.mark.parametrize("SchemaClass", [UserSchema, UserMetaSchema]) def test_only_in_init(SchemaClass, user): s = SchemaClass(only=("name", "age")).dump(user) assert "homepage" not in s assert "name" in s assert "age" in s def test_invalid_only_param(user): with pytest.raises(ValueError): UserSchema(only=("_invalid", "name")).dump(user) def test_can_serialize_uuid(serialized_user, user): assert serialized_user["uid"] == str(user.uid) def test_can_serialize_time(user, serialized_user): expected = user.time_registered.isoformat()[:15] assert serialized_user["time_registered"] == expected def test_json_module_is_deprecated(): with pytest.deprecated_call(): class UserJSONSchema(Schema): name = fields.String() class Meta: json_module = mockjson user = User("Joe") s = UserJSONSchema() result = s.dumps(user) assert result == mockjson.dumps("val") def test_render_module(): class UserJSONSchema(Schema): name = fields.String() class Meta: render_module = mockjson user = User("Joe") s = UserJSONSchema() result = s.dumps(user) assert result == mockjson.dumps("val") def test_custom_error_message(): class ErrorSchema(Schema): email = fields.Email(error_messages={"invalid": "Invalid email"}) homepage = fields.Url(error_messages={"invalid": "Bad homepage."}) balance = fields.Decimal(error_messages={"invalid": "Bad balance."}) u = {"email": "joe.net", "homepage": "joe@example.com", "balance": "blah"} s = ErrorSchema() with pytest.raises(ValidationError) as excinfo: s.load(u) errors = excinfo.value.messages assert "Bad balance." in errors["balance"] assert "Bad homepage." in errors["homepage"] assert "Invalid email" in errors["email"] def test_custom_unknown_error_message(): custom_message = "custom error message." class ErrorSchema(Schema): error_messages = {"unknown": custom_message} name = fields.String() s = ErrorSchema() u = {"name": "Joe", "age": 13} with pytest.raises(ValidationError) as excinfo: s.load(u) errors = excinfo.value.messages assert custom_message in errors["age"] def test_custom_type_error_message(): custom_message = "custom error message." class ErrorSchema(Schema): error_messages = {"type": custom_message} name = fields.String() s = ErrorSchema() u = ["Joe"] with pytest.raises(ValidationError) as excinfo: s.load(u) errors = excinfo.value.messages assert custom_message in errors["_schema"] def test_custom_type_error_message_with_many(): custom_message = "custom error message." class ErrorSchema(Schema): error_messages = {"type": custom_message} name = fields.String() s = ErrorSchema(many=True) u = {"name": "Joe"} with pytest.raises(ValidationError) as excinfo: s.load(u) errors = excinfo.value.messages assert custom_message in errors["_schema"] def test_custom_error_messages_with_inheritance(): parent_type_message = "parent type error message." parent_unknown_message = "parent unknown error message." child_type_message = "child type error message." class ParentSchema(Schema): error_messages = { "type": parent_type_message, "unknown": parent_unknown_message, } name = fields.String() class ChildSchema(ParentSchema): error_messages = {"type": child_type_message} unknown_user = {"name": "Eleven", "age": 12} type_user = 11 parent_schema = ParentSchema() with pytest.raises(ValidationError) as excinfo: parent_schema.load(unknown_user) assert parent_unknown_message in excinfo.value.messages["age"] with pytest.raises(ValidationError) as excinfo: parent_schema.load(type_user) assert parent_type_message in excinfo.value.messages["_schema"] child_schema = ChildSchema() with pytest.raises(ValidationError) as excinfo: child_schema.load(unknown_user) assert parent_unknown_message in excinfo.value.messages["age"] with pytest.raises(ValidationError) as excinfo: child_schema.load(type_user) assert child_type_message in excinfo.value.messages["_schema"] def test_load_errors_with_many(): class ErrorSchema(Schema): email = fields.Email() data = [ {"email": "bademail"}, {"email": "goo@email.com"}, {"email": "anotherbademail"}, ] with pytest.raises(ValidationError) as excinfo: ErrorSchema(many=True).load(data) errors = excinfo.value.messages assert 0 in errors assert 2 in errors assert "Not a valid email address." in errors[0]["email"][0] assert "Not a valid email address." in errors[2]["email"][0] def test_error_raised_if_fields_option_is_not_list(): with pytest.raises(ValueError): class BadSchema(Schema): name = fields.String() class Meta: fields = "name" def test_error_raised_if_additional_option_is_not_list(): with pytest.raises(ValueError): class BadSchema(Schema): name = fields.String() class Meta: additional = "email" def test_nested_custom_set_in_exclude_reusing_schema(): class CustomSet: # This custom set is to allow the obj check in BaseSchema.__filter_fields # to pass, since it'll be a valid instance, and this class overrides # getitem method to allow the hasattr check to pass too, which will try # to access the first obj index and will simulate a IndexError throwing. # e.g. SqlAlchemy.Query is a valid use case for this 'obj'. def __getitem__(self, item): return [][item] class ChildSchema(Schema): foo = fields.Raw(required=True) bar = fields.Raw() class Meta: only = ("bar",) class ParentSchema(Schema): child = fields.Nested(ChildSchema, many=True, exclude=("foo",)) sch = ParentSchema() obj = dict(child=CustomSet()) sch.dumps(obj) data = dict(child=[{"bar": 1}]) sch.load(data, partial=True) def test_nested_only(): class ChildSchema(Schema): foo = fields.Raw() bar = fields.Raw() baz = fields.Raw() class ParentSchema(Schema): bla = fields.Raw() bli = fields.Raw() blubb = fields.Nested(ChildSchema) sch = ParentSchema(only=("bla", "blubb.foo", "blubb.bar")) data = dict(bla=1, bli=2, blubb=dict(foo=42, bar=24, baz=242)) result = sch.dump(data) assert "bla" in result assert "blubb" in result assert "bli" not in result child = result["blubb"] assert "foo" in child assert "bar" in child assert "baz" not in child def test_nested_only_inheritance(): class ChildSchema(Schema): foo = fields.Raw() bar = fields.Raw() baz = fields.Raw() class ParentSchema(Schema): bla = fields.Raw() bli = fields.Raw() blubb = fields.Nested(ChildSchema, only=("foo", "bar")) sch = ParentSchema(only=("blubb.foo", "blubb.baz")) data = dict(bla=1, bli=2, blubb=dict(foo=42, bar=24, baz=242)) result = sch.dump(data) assert "bla" not in result assert "blubb" in result assert "bli" not in result child = result["blubb"] assert "foo" in child assert "bar" not in child assert "baz" not in child def test_nested_only_empty_inheritance(): class ChildSchema(Schema): foo = fields.Raw() bar = fields.Raw() baz = fields.Raw() class ParentSchema(Schema): bla = fields.Raw() bli = fields.Raw() blubb = fields.Nested(ChildSchema, only=("bar",)) sch = ParentSchema(only=("blubb.foo",)) data = dict(bla=1, bli=2, blubb=dict(foo=42, bar=24, baz=242)) result = sch.dump(data) assert "bla" not in result assert "blubb" in result assert "bli" not in result child = result["blubb"] assert "foo" not in child assert "bar" not in child assert "baz" not in child def test_nested_exclude(): class ChildSchema(Schema): foo = fields.Raw() bar = fields.Raw() baz = fields.Raw() class ParentSchema(Schema): bla = fields.Raw() bli = fields.Raw() blubb = fields.Nested(ChildSchema) sch = ParentSchema(exclude=("bli", "blubb.baz")) data = dict(bla=1, bli=2, blubb=dict(foo=42, bar=24, baz=242)) result = sch.dump(data) assert "bla" in result assert "blubb" in result assert "bli" not in result child = result["blubb"] assert "foo" in child assert "bar" in child assert "baz" not in child def test_nested_exclude_inheritance(): class ChildSchema(Schema): foo = fields.Raw() bar = fields.Raw() baz = fields.Raw() class ParentSchema(Schema): bla = fields.Raw() bli = fields.Raw() blubb = fields.Nested(ChildSchema, exclude=("baz",)) sch = ParentSchema(exclude=("blubb.foo",)) data = dict(bla=1, bli=2, blubb=dict(foo=42, bar=24, baz=242)) result = sch.dump(data) assert "bla" in result assert "blubb" in result assert "bli" in result child = result["blubb"] assert "foo" not in child assert "bar" in child assert "baz" not in child def test_nested_only_and_exclude(): class ChildSchema(Schema): foo = fields.Raw() bar = fields.Raw() baz = fields.Raw() class ParentSchema(Schema): bla = fields.Raw() bli = fields.Raw() blubb = fields.Nested(ChildSchema) sch = ParentSchema(only=("bla", "blubb.foo", "blubb.bar"), exclude=("blubb.foo",)) data = dict(bla=1, bli=2, blubb=dict(foo=42, bar=24, baz=242)) result = sch.dump(data) assert "bla" in result assert "blubb" in result assert "bli" not in result child = result["blubb"] assert "foo" not in child assert "bar" in child assert "baz" not in child def test_nested_only_then_exclude_inheritance(): class ChildSchema(Schema): foo = fields.Raw() bar = fields.Raw() baz = fields.Raw() class ParentSchema(Schema): bla = fields.Raw() bli = fields.Raw() blubb = fields.Nested(ChildSchema, only=("foo", "bar")) sch = ParentSchema(exclude=("blubb.foo",)) data = dict(bla=1, bli=2, blubb=dict(foo=42, bar=24, baz=242)) result = sch.dump(data) assert "bla" in result assert "blubb" in result assert "bli" in result child = result["blubb"] assert "foo" not in child assert "bar" in child assert "baz" not in child def test_nested_exclude_then_only_inheritance(): class ChildSchema(Schema): foo = fields.Raw() bar = fields.Raw() baz = fields.Raw() class ParentSchema(Schema): bla = fields.Raw() bli = fields.Raw() blubb = fields.Nested(ChildSchema, exclude=("foo",)) sch = ParentSchema(only=("blubb.bar",)) data = dict(bla=1, bli=2, blubb=dict(foo=42, bar=24, baz=242)) result = sch.dump(data) assert "bla" not in result assert "blubb" in result assert "bli" not in result child = result["blubb"] assert "foo" not in child assert "bar" in child assert "baz" not in child def test_nested_exclude_and_only_inheritance(): class ChildSchema(Schema): foo = fields.Raw() bar = fields.Raw() baz = fields.Raw() ban = fields.Raw() fuu = fields.Raw() class ParentSchema(Schema): bla = fields.Raw() bli = fields.Raw() blubb = fields.Nested( ChildSchema, only=("foo", "bar", "baz", "ban"), exclude=("foo",) ) sch = ParentSchema( only=("blubb.foo", "blubb.bar", "blubb.baz"), exclude=("blubb.baz",) ) data = dict(bla=1, bli=2, blubb=dict(foo=42, bar=24, baz=242)) result = sch.dump(data) assert "bla" not in result assert "blubb" in result assert "bli" not in result child = result["blubb"] assert "foo" not in child assert "bar" in child assert "baz" not in child assert "ban" not in child assert "fuu" not in child # https://github.com/marshmallow-code/marshmallow/issues/1160 def test_nested_instance_many(): class BookSchema(Schema): id = fields.Int() title = fields.String() class UserSchema(Schema): id = fields.Int() name = fields.String() books = fields.Nested(BookSchema(many=True)) books = [{"id": 1, "title": "First book"}, {"id": 2, "title": "Second book"}] user = {"id": 1, "name": "Peter", "books": books} user_dump = UserSchema().dump(user) assert user_dump["books"] == books user_load = UserSchema().load(user_dump) assert user_load == user def test_nested_instance_only(): class ArtistSchema(Schema): first = fields.Str() last = fields.Str() class AlbumSchema(Schema): title = fields.Str() artist = fields.Nested(ArtistSchema(), only=("last",)) schema = AlbumSchema() album = {"title": "Hunky Dory", "artist": {"last": "Bowie"}} loaded = schema.load(album) assert loaded == album full_album = {"title": "Hunky Dory", "artist": {"first": "David", "last": "Bowie"}} assert schema.dump(full_album) == album def test_nested_instance_exclude(): class ArtistSchema(Schema): first = fields.Str() last = fields.Str() class AlbumSchema(Schema): title = fields.Str() artist = fields.Nested(ArtistSchema(), exclude=("first",)) schema = AlbumSchema() album = {"title": "Hunky Dory", "artist": {"last": "Bowie"}} loaded = schema.load(album) assert loaded == album full_album = {"title": "Hunky Dory", "artist": {"first": "David", "last": "Bowie"}} assert schema.dump(full_album) == album def test_meta_nested_exclude(): class ChildSchema(Schema): foo = fields.Raw() bar = fields.Raw() baz = fields.Raw() class ParentSchema(Schema): bla = fields.Raw() bli = fields.Raw() blubb = fields.Nested(ChildSchema) class Meta: exclude = ("blubb.foo",) data = dict(bla=1, bli=2, blubb=dict(foo=42, bar=24, baz=242)) sch = ParentSchema() result = sch.dump(data) assert "bla" in result assert "blubb" in result assert "bli" in result child = result["blubb"] assert "foo" not in child assert "bar" in child assert "baz" in child # Test fields with dot notations in Meta.exclude on multiple instantiations # Regression test for https://github.com/marshmallow-code/marshmallow/issues/1212 sch = ParentSchema() result = sch.dump(data) assert "bla" in result assert "blubb" in result assert "bli" in result child = result["blubb"] assert "foo" not in child assert "bar" in child assert "baz" in child def test_nested_custom_set_not_implementing_getitem(): # This test checks that marshmallow can serialize implementations of # :mod:`collections.abc.MutableSequence`, with ``__getitem__`` arguments # that are not integers. class ListLikeParent: """ Implements a list-like object that can get children using a non-integer key """ def __init__(self, required_key, child): """ :param required_key: The key to use in ``__getitem__`` in order to successfully get the ``child`` :param child: The return value of the ``child`` if ``__getitem__`` succeeds """ self.children = {required_key: child} class Child: """ Implements an object with some attribute """ def __init__(self, attribute: str): """ :param attribute: The attribute to initialize """ self.attribute = attribute class ChildSchema(Schema): """ The marshmallow schema for the child """ attribute = fields.Str() class ParentSchema(Schema): """ The schema for the parent """ children = fields.Nested(ChildSchema, many=True) attribute = "Foo" required_key = "key" child = Child(attribute) parent = ListLikeParent(required_key, child) ParentSchema().dump(parent) def test_deeply_nested_only_and_exclude(): class GrandChildSchema(Schema): goo = fields.Raw() gah = fields.Raw() bah = fields.Raw() class ChildSchema(Schema): foo = fields.Raw() bar = fields.Raw() flubb = fields.Nested(GrandChildSchema) class ParentSchema(Schema): bla = fields.Raw() bli = fields.Raw() blubb = fields.Nested(ChildSchema) sch = ParentSchema( only=("bla", "blubb.foo", "blubb.flubb.goo", "blubb.flubb.gah"), exclude=("blubb.flubb.goo",), ) data = dict(bla=1, bli=2, blubb=dict(foo=3, bar=4, flubb=dict(goo=5, gah=6, bah=7))) result = sch.dump(data) assert "bla" in result assert "blubb" in result assert "bli" not in result child = result["blubb"] assert "foo" in child assert "flubb" in child assert "bar" not in child grand_child = child["flubb"] assert "gah" in grand_child assert "goo" not in grand_child assert "bah" not in grand_child def test_nested_lambda(): class ChildSchema(Schema): id = fields.Str() name = fields.Str() parent = fields.Nested(lambda: ParentSchema(only=("id",)), dump_only=True) siblings = fields.List(fields.Nested(lambda: ChildSchema(only=("id", "name")))) class ParentSchema(Schema): id = fields.Str() spouse = fields.Nested(lambda: ParentSchema(only=("id",))) children = fields.List( fields.Nested(lambda: ChildSchema(only=("id", "parent", "siblings"))) ) sch = ParentSchema() data_to_load = { "id": "p1", "spouse": {"id": "p2"}, "children": [{"id": "c1", "siblings": [{"id": "c2", "name": "sis"}]}], } loaded = sch.load(data_to_load) assert loaded == data_to_load data_to_dump = dict( id="p2", spouse=dict(id="p2"), children=[ dict( id="c1", name="bar", parent=dict(id="p2"), siblings=[dict(id="c2", name="sis")], ) ], ) dumped = sch.dump(data_to_dump) assert dumped == { "id": "p2", "spouse": {"id": "p2"}, "children": [ { "id": "c1", "parent": {"id": "p2"}, "siblings": [{"id": "c2", "name": "sis"}], } ], } @pytest.mark.parametrize("data_key", ("f1", "f5", None)) def test_data_key_collision(data_key): class MySchema(Schema): f1 = fields.Raw() f2 = fields.Raw(data_key=data_key) f3 = fields.Raw(data_key="f5") f4 = fields.Raw(data_key="f1", load_only=True) if data_key is None: MySchema() else: with pytest.raises(ValueError, match=data_key): MySchema() @pytest.mark.parametrize("attribute", ("f1", "f5", None)) def test_attribute_collision(attribute): class MySchema(Schema): f1 = fields.Raw() f2 = fields.Raw(attribute=attribute) f3 = fields.Raw(attribute="f5") f4 = fields.Raw(attribute="f1", dump_only=True) if attribute is None: MySchema() else: with pytest.raises(ValueError, match=attribute): MySchema() class TestDeeplyNestedLoadOnly: @pytest.fixture def schema(self): class GrandChildSchema(Schema): str_dump_only = fields.String() str_load_only = fields.String() str_regular = fields.String() class ChildSchema(Schema): str_dump_only = fields.String() str_load_only = fields.String() str_regular = fields.String() grand_child = fields.Nested(GrandChildSchema, unknown=EXCLUDE) class ParentSchema(Schema): str_dump_only = fields.String() str_load_only = fields.String() str_regular = fields.String() child = fields.Nested(ChildSchema, unknown=EXCLUDE) return ParentSchema( dump_only=( "str_dump_only", "child.str_dump_only", "child.grand_child.str_dump_only", ), load_only=( "str_load_only", "child.str_load_only", "child.grand_child.str_load_only", ), ) @pytest.fixture def data(self): return dict( str_dump_only="Dump Only", str_load_only="Load Only", str_regular="Regular String", child=dict( str_dump_only="Dump Only", str_load_only="Load Only", str_regular="Regular String", grand_child=dict( str_dump_only="Dump Only", str_load_only="Load Only", str_regular="Regular String", ), ), ) def test_load_only(self, schema, data): result = schema.dump(data) assert "str_load_only" not in result assert "str_dump_only" in result assert "str_regular" in result child = result["child"] assert "str_load_only" not in child assert "str_dump_only" in child assert "str_regular" in child grand_child = child["grand_child"] assert "str_load_only" not in grand_child assert "str_dump_only" in grand_child assert "str_regular" in grand_child def test_dump_only(self, schema, data): result = schema.load(data, unknown=EXCLUDE) assert "str_dump_only" not in result assert "str_load_only" in result assert "str_regular" in result child = result["child"] assert "str_dump_only" not in child assert "str_load_only" in child assert "str_regular" in child grand_child = child["grand_child"] assert "str_dump_only" not in grand_child assert "str_load_only" in grand_child assert "str_regular" in grand_child class TestDeeplyNestedListLoadOnly: @pytest.fixture def schema(self): class ChildSchema(Schema): str_dump_only = fields.String() str_load_only = fields.String() str_regular = fields.String() class ParentSchema(Schema): str_dump_only = fields.String() str_load_only = fields.String() str_regular = fields.String() child = fields.List(fields.Nested(ChildSchema, unknown=EXCLUDE)) return ParentSchema( dump_only=("str_dump_only", "child.str_dump_only"), load_only=("str_load_only", "child.str_load_only"), ) @pytest.fixture def data(self): return dict( str_dump_only="Dump Only", str_load_only="Load Only", str_regular="Regular String", child=[ dict( str_dump_only="Dump Only", str_load_only="Load Only", str_regular="Regular String", ) ], ) def test_load_only(self, schema, data): result = schema.dump(data) assert "str_load_only" not in result assert "str_dump_only" in result assert "str_regular" in result child = result["child"][0] assert "str_load_only" not in child assert "str_dump_only" in child assert "str_regular" in child def test_dump_only(self, schema, data): result = schema.load(data, unknown=EXCLUDE) assert "str_dump_only" not in result assert "str_load_only" in result assert "str_regular" in result child = result["child"][0] assert "str_dump_only" not in child assert "str_load_only" in child assert "str_regular" in child def test_nested_constructor_only_and_exclude(): class GrandChildSchema(Schema): goo = fields.Raw() gah = fields.Raw() bah = fields.Raw() class ChildSchema(Schema): foo = fields.Raw() bar = fields.Raw() flubb = fields.Nested(GrandChildSchema) class ParentSchema(Schema): bla = fields.Raw() bli = fields.Raw() blubb = fields.Nested( ChildSchema, only=("foo", "flubb.goo", "flubb.gah"), exclude=("flubb.goo",) ) sch = ParentSchema(only=("bla", "blubb")) data = dict(bla=1, bli=2, blubb=dict(foo=3, bar=4, flubb=dict(goo=5, gah=6, bah=7))) result = sch.dump(data) assert "bla" in result assert "blubb" in result assert "bli" not in result child = result["blubb"] assert "foo" in child assert "flubb" in child assert "bar" not in child grand_child = child["flubb"] assert "gah" in grand_child assert "goo" not in grand_child assert "bah" not in grand_child def test_only_and_exclude(): class MySchema(Schema): foo = fields.Raw() bar = fields.Raw() baz = fields.Raw() sch = MySchema(only=("foo", "bar"), exclude=("bar",)) data = dict(foo=42, bar=24, baz=242) result = sch.dump(data) assert "foo" in result assert "bar" not in result def test_only_and_exclude_with_fields(): class MySchema(Schema): foo = fields.Raw() class Meta: fields = ("bar", "baz") sch = MySchema(only=("bar", "baz"), exclude=("bar",)) data = dict(foo=42, bar=24, baz=242) result = sch.dump(data) assert "baz" in result assert "bar" not in result def test_invalid_only_and_exclude_with_fields(): class MySchema(Schema): foo = fields.Raw() class Meta: fields = ("bar", "baz") with pytest.raises(ValueError) as excinfo: MySchema(only=("foo", "par"), exclude=("ban",)) assert "foo" in str(excinfo.value) assert "par" in str(excinfo.value) assert "ban" in str(excinfo.value) def test_only_and_exclude_with_additional(): class MySchema(Schema): foo = fields.Raw() class Meta: additional = ("bar", "baz") sch = MySchema(only=("foo", "bar"), exclude=("bar",)) data = dict(foo=42, bar=24, baz=242) result = sch.dump(data) assert "foo" in result assert "bar" not in result def test_invalid_only_and_exclude_with_additional(): class MySchema(Schema): foo = fields.Raw() class Meta: additional = ("bar", "baz") with pytest.raises(ValueError) as excinfo: MySchema(only=("foop", "par"), exclude=("ban",)) assert "foop" in str(excinfo.value) assert "par" in str(excinfo.value) assert "ban" in str(excinfo.value) def test_exclude_invalid_attribute(): class MySchema(Schema): foo = fields.Raw() with pytest.raises(ValueError, match="'bar'"): MySchema(exclude=("bar",)) def test_only_bounded_by_fields(): class MySchema(Schema): class Meta: fields = ("foo",) with pytest.raises(ValueError, match="'baz'"): MySchema(only=("baz",)) def test_only_bounded_by_additional(): class MySchema(Schema): class Meta: additional = ("b",) with pytest.raises(ValueError): MySchema(only=("c",)).dump({"c": 3}) def test_only_empty(): class MySchema(Schema): foo = fields.Raw() sch = MySchema(only=()) assert "foo" not in sch.dump({"foo": "bar"}) @pytest.mark.parametrize("param", ("only", "exclude")) def test_only_and_exclude_as_string(param): class MySchema(Schema): foo = fields.Raw() with pytest.raises(StringNotCollectionError): MySchema(**{param: "foo"}) def test_nested_with_sets(): class Inner(Schema): foo = fields.Raw() class Outer(Schema): inners = fields.Nested(Inner, many=True) sch = Outer() class Thing(NamedTuple): foo: int data = dict(inners={Thing(42), Thing(2)}) result = sch.dump(data) assert len(result["inners"]) == 2 def test_meta_serializer_fields(): u = User("John", age=42.3, email="john@example.com", homepage="http://john.com") result = UserMetaSchema().dump(u) assert result["name"] == u.name assert result["balance"] == decimal.Decimal("100.00") assert result["uppername"] == "JOHN" assert result["is_old"] is False assert result["created"] == utils.isoformat(u.created) assert result["finger_count"] == 10 assert result["various_data"] == dict(u.various_data) def test_meta_fields_mapping(user): s = UserMetaSchema() s.dump(user) # need to call dump to update fields assert type(s.fields["balance"]) is fields.Decimal # Inferred fields assert type(s.fields["name"]._field_cache[fields.String]) is fields.String assert type(s.fields["created"]._field_cache[fields.DateTime]) is fields.DateTime assert type(s.fields["updated"]._field_cache[fields.DateTime]) is fields.DateTime assert type(s.fields["age"]._field_cache[fields.Float]) is fields.Float assert type(s.fields["registered"]._field_cache[fields.Boolean]) is fields.Boolean assert type(s.fields["sex_choices"]._field_cache[fields.Raw]) is fields.Raw assert type(s.fields["hair_colors"]._field_cache[fields.Raw]) is fields.Raw assert type(s.fields["finger_count"]._field_cache[fields.Integer]) is fields.Integer assert type(s.fields["uid"]._field_cache[fields.UUID]) is fields.UUID assert type(s.fields["time_registered"]._field_cache[fields.Time]) is fields.Time assert type(s.fields["birthdate"]._field_cache[fields.Date]) is fields.Date assert ( type(s.fields["since_created"]._field_cache[fields.TimeDelta]) is fields.TimeDelta ) def test_meta_field_not_on_obj_raises_attribute_error(user): class BadUserSchema(Schema): class Meta: fields = ("name",) exclude = ("notfound",) with pytest.raises(ValueError, match="'notfound'"): BadUserSchema().dump(user) def test_exclude_fields(user): s = UserExcludeSchema().dump(user) assert "created" not in s assert "updated" not in s assert "name" in s def test_fields_option_must_be_list_or_tuple(): with pytest.raises(ValueError): class BadFields(Schema): class Meta: fields = "name" def test_exclude_option_must_be_list_or_tuple(): with pytest.raises(ValueError): class BadExclude(Schema): class Meta: exclude = "name" def test_datetimeformat_option(user): meta_fmt = "%Y-%m" field_fmt = "%m-%d" class DateTimeFormatSchema(Schema): updated = fields.DateTime(field_fmt) class Meta: fields = ("created", "updated") datetimeformat = meta_fmt serialized = DateTimeFormatSchema().dump(user) assert serialized["created"] == user.created.strftime(meta_fmt) assert serialized["updated"] == user.updated.strftime(field_fmt) def test_dateformat_option(user): fmt = "%Y-%m" field_fmt = "%m-%d" class DateFormatSchema(Schema): birthdate = fields.Date(field_fmt) class Meta: fields = ("birthdate", "activation_date") dateformat = fmt serialized = DateFormatSchema().dump(user) assert serialized["birthdate"] == user.birthdate.strftime(field_fmt) assert serialized["activation_date"] == user.activation_date.strftime(fmt) def test_timeformat_option(user): fmt = "%H:%M:%S" field_fmt = "%H:%M" class TimeFormatSchema(Schema): birthtime = fields.Time(field_fmt) class Meta: fields = ("birthtime", "time_registered") timeformat = fmt serialized = TimeFormatSchema().dump(user) assert serialized["birthtime"] == user.birthtime.strftime(field_fmt) assert serialized["time_registered"] == user.time_registered.strftime(fmt) def test_default_dateformat(user): class DateFormatSchema(Schema): updated = fields.DateTime(format="%m-%d") class Meta: fields = ("created", "updated") serialized = DateFormatSchema().dump(user) assert serialized["created"] == utils.isoformat(user.created) assert serialized["updated"] == user.updated.strftime("%m-%d") def test_inherit_meta(user): class InheritedMetaSchema(UserMetaSchema): pass result = InheritedMetaSchema().dump(user) expected = UserMetaSchema().dump(user) assert result == expected def test_inherit_meta_override(): class Parent(Schema): class Meta: fields = ("name", "email") dump_only = ("name",) class Child(Schema): class Meta(Parent.Meta): dump_only = ("name", "email") child = Child() assert child.opts.fields == ("name", "email") assert child.opts.dump_only == ("name", "email") def test_additional(user): s = UserAdditionalSchema().dump(user) assert s["lowername"] == user.name.lower() assert s["name"] == user.name def test_cant_set_both_additional_and_fields(user): with pytest.raises(ValueError): class BadSchema(Schema): name = fields.String() class Meta: fields = ("name", "email") additional = ("email", "homepage") def test_serializing_none_meta(): s = UserMetaSchema().dump(None) assert s == {} class CustomError(Exception): pass class MySchema(Schema): name = fields.String() email = fields.Email() age = fields.Integer() def handle_error(self, errors, obj, **kwargs): raise CustomError("Something bad happened") def test_load_with_custom_error_handler(self): in_data = {"email": "invalid"} class MySchema3(Schema): email = fields.Email() def handle_error(self, error, data, **kwargs): assert type(error) is ValidationError assert "email" in error.messages assert list(error.messages.keys()) == ["email"] assert data == in_data raise CustomError("Something bad happened") with pytest.raises(CustomError): MySchema3().load(in_data) def test_load_with_custom_error_handler_and_partially_valid_data(self): in_data = {"email": "invalid", "url": "http://valid.com"} class MySchema(Schema): email = fields.Email() url = fields.URL() def handle_error(self, error, data, **kwargs): assert type(error) is ValidationError assert "email" in error.messages assert list(error.messages.keys()) == ["email"] assert data == in_data raise CustomError("Something bad happened") with pytest.raises(CustomError): MySchema().load(in_data) def test_custom_error_handler_with_validates_decorator(self): in_data = {"num": -1} class MySchema(Schema): num = fields.Int() @validates("num") def validate_num(self, value): if value < 0: raise ValidationError("Must be greater than 0.") def handle_error(self, error, data, **kwargs): assert type(error) is ValidationError assert "num" in error.messages assert list(error.messages.keys()) == ["num"] assert data == in_data raise CustomError("Something bad happened") with pytest.raises(CustomError): MySchema().load(in_data) def test_custom_error_handler_with_validates_schema_decorator(self): in_data = {"num": -1} class MySchema(Schema): num = fields.Int() @validates_schema def validates_schema(self, data, **kwargs): raise ValidationError("Invalid schema!") def handle_error(self, error, data, **kwargs): assert type(error) is ValidationError assert list(error.messages.keys()) == ["_schema"] assert data == in_data raise CustomError("Something bad happened") with pytest.raises(CustomError): MySchema().load(in_data) def test_validate_with_custom_error_handler(self): with pytest.raises(CustomError): MySchema().validate({"age": "notvalid", "email": "invalid"}) class TestFieldValidation: def test_errors_are_cleared_after_loading_collection(self): def always_fail(val): raise ValidationError("lol") class MySchema(Schema): foo = fields.Str(validate=always_fail) schema = MySchema() with pytest.raises(ValidationError) as excinfo: schema.load([{"foo": "bar"}, {"foo": "baz"}], many=True) errors = excinfo.value.messages assert len(errors[0]["foo"]) == 1 assert len(errors[1]["foo"]) == 1 with pytest.raises(ValidationError) as excinfo: schema.load({"foo": "bar"}) errors = excinfo.value.messages assert len(errors["foo"]) == 1 def test_raises_error_with_list(self): def validator(val): raise ValidationError(["err1", "err2"]) class MySchema(Schema): foo = fields.Raw(validate=validator) s = MySchema() errors = s.validate({"foo": 42}) assert errors["foo"] == ["err1", "err2"] # https://github.com/marshmallow-code/marshmallow/issues/110 def test_raises_error_with_dict(self): def validator(val): raise ValidationError({"code": "invalid_foo"}) class MySchema(Schema): foo = fields.Raw(validate=validator) s = MySchema() errors = s.validate({"foo": 42}) assert errors["foo"] == [{"code": "invalid_foo"}] def test_ignored_if_not_in_only(self): class MySchema(Schema): a = fields.Raw() b = fields.Raw() @validates("a") def validate_a(self, val): raise ValidationError({"code": "invalid_a"}) @validates("b") def validate_b(self, val): raise ValidationError({"code": "invalid_b"}) s = MySchema(only=("b",)) errors = s.validate({"b": "data"}) assert errors == {"b": {"code": "invalid_b"}} def test_schema_repr(): class MySchema(Schema): name = fields.String() ser = MySchema(many=True) rep = repr(ser) assert "MySchema" in rep assert "many=True" in rep class TestNestedSchema: @pytest.fixture def user(self): return User(name="Monty", age=81) @pytest.fixture def blog(self, user): col1 = User(name="Mick", age=123) col2 = User(name="Keith", age=456) return Blog( "Monty's blog", user=user, categories=["humor", "violence"], collaborators=[col1, col2], ) # regression test for https://github.com/marshmallow-code/marshmallow/issues/64 def test_nested_many_with_missing_attribute(self, user): class SimpleBlogSchema(Schema): title = fields.Str() wat = fields.Nested(UserSchema, many=True) blog = Blog("Simple blog", user=user, collaborators=None) schema = SimpleBlogSchema() result = schema.dump(blog) assert "wat" not in result def test_nested_with_attribute_none(self): class InnerSchema(Schema): bar = fields.Raw() class MySchema(Schema): foo = fields.Nested(InnerSchema) class MySchema2(Schema): foo = fields.Nested(InnerSchema) s = MySchema() result = s.dump({"foo": None}) assert result["foo"] is None s2 = MySchema2() result2 = s2.dump({"foo": None}) assert result2["foo"] is None def test_nested_field_does_not_validate_required(self): class BlogRequiredSchema(Schema): user = fields.Nested(UserSchema, required=True) b = Blog("Authorless blog", user=None) BlogRequiredSchema().dump(b) def test_nested_none(self): class BlogDefaultSchema(Schema): user = fields.Nested(UserSchema, dump_default=0) b = Blog("Just the default blog", user=None) data = BlogDefaultSchema().dump(b) assert data["user"] is None def test_nested(self, user, blog): blog_serializer = BlogSchema() serialized_blog = blog_serializer.dump(blog) user_serializer = UserSchema() serialized_user = user_serializer.dump(user) assert serialized_blog["user"] == serialized_user with pytest.raises(ValidationError, match="email"): BlogSchema().load( {"title": "Monty's blog", "user": {"name": "Monty", "email": "foo"}} ) def test_nested_many_fields(self, blog): serialized_blog = BlogSchema().dump(blog) expected = [UserSchema().dump(col) for col in blog.collaborators] assert serialized_blog["collaborators"] == expected def test_nested_meta_many(self, blog): serialized_blog = BlogUserMetaSchema().dump(blog) assert len(serialized_blog["collaborators"]) == 2 expected = [UserMetaSchema().dump(col) for col in blog.collaborators] assert serialized_blog["collaborators"] == expected def test_nested_only(self, blog): col1 = User(name="Mick", age=123, id_="abc") col2 = User(name="Keith", age=456, id_="def") blog.collaborators = [col1, col2] serialized_blog = BlogOnlySchema().dump(blog) assert serialized_blog["collaborators"] == [{"id": col1.id}, {"id": col2.id}] def test_exclude(self, blog): serialized = BlogSchemaExclude().dump(blog) assert "uppername" not in serialized["user"] def test_list_field(self, blog): serialized = BlogSchema().dump(blog) assert serialized["categories"] == ["humor", "violence"] def test_nested_load_many(self): in_data = { "title": "Shine A Light", "collaborators": [ {"name": "Mick", "email": "mick@stones.com"}, {"name": "Keith", "email": "keith@stones.com"}, ], } data = BlogSchema().load(in_data) collabs = data["collaborators"] assert len(collabs) == 2 assert all(type(each) is User for each in collabs) assert collabs[0].name == in_data["collaborators"][0]["name"] def test_nested_errors(self): with pytest.raises(ValidationError) as excinfo: BlogSchema().load( {"title": "Monty's blog", "user": {"name": "Monty", "email": "foo"}} ) errors = excinfo.value.messages assert "email" in errors["user"] assert len(errors["user"]["email"]) == 1 assert "Not a valid email address." in errors["user"]["email"][0] # No problems with collaborators assert "collaborators" not in errors def test_nested_method_field(self, blog): data = BlogSchema().dump(blog) assert data["user"]["is_old"] assert data["collaborators"][0]["is_old"] def test_nested_function_field(self, blog, user): data = BlogSchema().dump(blog) assert data["user"]["lowername"] == user.name.lower() expected = blog.collaborators[0].name.lower() assert data["collaborators"][0]["lowername"] == expected def test_serializer_meta_with_nested_fields(self, blog, user): data = BlogSchemaMeta().dump(blog) assert data["title"] == blog.title assert data["user"] == UserSchema().dump(user) assert data["collaborators"] == [ UserSchema().dump(c) for c in blog.collaborators ] assert data["categories"] == blog.categories def test_serializer_with_nested_meta_fields(self, blog): # Schema has user = fields.Nested(UserMetaSerializer) s = BlogUserMetaSchema().dump(blog) assert s["user"] == UserMetaSchema().dump(blog.user) def test_nested_fields_must_be_passed_a_serializer(self, blog): class BadNestedFieldSchema(BlogSchema): user = fields.Nested(fields.String) with pytest.raises(ValueError): BadNestedFieldSchema().dump(blog) # regression test for https://github.com/marshmallow-code/marshmallow/issues/188 def test_invalid_type_passed_to_nested_field(self): class InnerSchema(Schema): foo = fields.Raw() class MySchema(Schema): inner = fields.Nested(InnerSchema, many=True) sch = MySchema() sch.load({"inner": [{"foo": 42}]}) with pytest.raises(ValidationError) as excinfo: sch.load({"inner": "invalid"}) errors = excinfo.value.messages assert "inner" in errors assert errors["inner"] == ["Invalid type."] class OuterSchema(Schema): inner = fields.Nested(InnerSchema) schema = OuterSchema() with pytest.raises(ValidationError) as excinfo: schema.load({"inner": 1}) errors = excinfo.value.messages assert errors["inner"]["_schema"] == ["Invalid input type."] # regression test for https://github.com/marshmallow-code/marshmallow/issues/298 def test_all_errors_on_many_nested_field_with_validates_decorator(self): class Inner(Schema): req = fields.Raw(required=True) class Outer(Schema): inner = fields.Nested(Inner, many=True) @validates("inner") def validates_inner(self, data): raise ValidationError("not a chance") outer = Outer() with pytest.raises(ValidationError) as excinfo: outer.load({"inner": [{}]}) errors = excinfo.value.messages assert "inner" in errors assert "_schema" in errors["inner"] @pytest.mark.parametrize("unknown", (None, RAISE, INCLUDE, EXCLUDE)) def test_nested_unknown_validation(self, unknown): class ChildSchema(Schema): num = fields.Int() class ParentSchema(Schema): child = fields.Nested(ChildSchema, unknown=unknown) data = {"child": {"num": 1, "extra": 1}} if unknown is None or unknown == RAISE: with pytest.raises(ValidationError) as exc: ParentSchema().load(data) assert exc.value.messages == {"child": {"extra": ["Unknown field."]}} else: output = { INCLUDE: {"child": {"num": 1, "extra": 1}}, EXCLUDE: {"child": {"num": 1}}, }[unknown] assert ParentSchema().load(data) == output class TestPluckSchema: @pytest.mark.parametrize("user_schema", [UserSchema, UserSchema()]) def test_pluck(self, user_schema, blog): class FlatBlogSchema(Schema): user = fields.Pluck(user_schema, "name") collaborators = fields.Pluck(user_schema, "name", many=True) s = FlatBlogSchema() data = s.dump(blog) assert data["user"] == blog.user.name for i, name in enumerate(data["collaborators"]): assert name == blog.collaborators[i].name def test_pluck_none(self, blog): class FlatBlogSchema(Schema): user = fields.Pluck(UserSchema, "name") collaborators = fields.Pluck(UserSchema, "name", many=True) col1 = User(name="Mick", age=123) col2 = User(name="Keith", age=456) blog = Blog(title="Unowned Blog", user=None, collaborators=[col1, col2]) s = FlatBlogSchema() data = s.dump(blog) assert data["user"] == blog.user for i, name in enumerate(data["collaborators"]): assert name == blog.collaborators[i].name # Regression test for https://github.com/marshmallow-code/marshmallow/issues/800 def test_pluck_with_data_key(self, blog): class UserSchema(Schema): name = fields.String(data_key="username") age = fields.Int() class FlatBlogSchema(Schema): user = fields.Pluck(UserSchema, "name") collaborators = fields.Pluck(UserSchema, "name", many=True) s = FlatBlogSchema() data = s.dump(blog) assert data["user"] == blog.user.name for i, name in enumerate(data["collaborators"]): assert name == blog.collaborators[i].name assert s.load(data) == { "user": {"name": "Monty"}, "collaborators": [{"name": "Mick"}, {"name": "Keith"}], } class TestSelfReference: @pytest.fixture def employer(self): return User(name="Joe", age=59) @pytest.fixture def user(self, employer): return User(name="Tom", employer=employer, age=28) def test_nesting_schema_by_passing_lambda(self, user, employer): class SelfReferencingSchema(Schema): name = fields.Str() age = fields.Int() employer = fields.Nested( lambda: SelfReferencingSchema(exclude=("employer",)) ) data = SelfReferencingSchema().dump(user) assert data["name"] == user.name assert data["employer"]["name"] == employer.name assert data["employer"]["age"] == employer.age def test_nesting_schema_by_passing_class_name(self, user, employer): class SelfReferencingSchema(Schema): name = fields.Str() age = fields.Int() employer = fields.Nested("SelfReferencingSchema", exclude=("employer",)) data = SelfReferencingSchema().dump(user) assert data["name"] == user.name assert data["employer"]["name"] == employer.name assert data["employer"]["age"] == employer.age def test_nesting_schema_self_string(self, user, employer): with pytest.warns( DeprecationWarning, match="Passing 'self' to `Nested` is deprecated" ): class SelfSchema(Schema): name = fields.String() age = fields.Integer() employer = fields.Nested("self", exclude=("employer",)) data = SelfSchema().dump(user) assert data["name"] == user.name assert data["employer"]["name"] == employer.name assert data["employer"]["age"] == employer.age def test_nesting_within_itself_meta(self, user, employer): class SelfSchema(Schema): employer = fields.Nested(lambda: SelfSchema(exclude=("employer",))) class Meta: additional = ("name", "age") data = SelfSchema().dump(user) assert data["name"] == user.name assert data["age"] == user.age assert data["employer"]["name"] == employer.name assert data["employer"]["age"] == employer.age def test_nested_self_with_only_param(self, user, employer): class SelfSchema(Schema): employer = fields.Nested(lambda: SelfSchema(only=("name",))) class Meta: fields = ("name", "employer") data = SelfSchema().dump(user) assert data["name"] == user.name assert data["employer"]["name"] == employer.name assert "age" not in data["employer"] def test_multiple_pluck_self_lambda(self, user): class MultipleSelfSchema(Schema): emp = fields.Pluck( lambda: MultipleSelfSchema(), "name", attribute="employer" ) rels = fields.Pluck( lambda: MultipleSelfSchema(), "name", many=True, attribute="relatives" ) class Meta: fields = ("name", "emp", "rels") schema = MultipleSelfSchema() user.relatives = [User(name="Bar", age=12), User(name="Baz", age=34)] data = schema.dump(user) assert len(data["rels"]) == len(user.relatives) relative = data["rels"][0] assert relative == user.relatives[0].name def test_multiple_pluck_self_string(self, user): with pytest.warns( DeprecationWarning, match="Passing 'self' to `Nested` is deprecated" ): class MultipleSelfSchema(Schema): emp = fields.Pluck("self", "name", attribute="employer") rels = fields.Pluck("self", "name", many=True, attribute="relatives") class Meta: fields = ("name", "emp", "rels") schema = MultipleSelfSchema() user.relatives = [User(name="Bar", age=12), User(name="Baz", age=34)] data = schema.dump(user) assert len(data["rels"]) == len(user.relatives) relative = data["rels"][0] assert relative == user.relatives[0].name def test_nested_self_many_lambda(self): class SelfManySchema(Schema): relatives = fields.Nested(lambda: SelfManySchema(), many=True) class Meta: additional = ("name", "age") person = User(name="Foo") person.relatives = [User(name="Bar", age=12), User(name="Baz", age=34)] data = SelfManySchema().dump(person) assert data["name"] == person.name assert len(data["relatives"]) == len(person.relatives) assert data["relatives"][0]["name"] == person.relatives[0].name assert data["relatives"][0]["age"] == person.relatives[0].age def test_nested_self_many_string(self): with pytest.warns( DeprecationWarning, match="Passing 'self' to `Nested` is deprecated" ): class SelfManySchema(Schema): relatives = fields.Nested("self", many=True) class Meta: additional = ("name", "age") person = User(name="Foo") person.relatives = [User(name="Bar", age=12), User(name="Baz", age=34)] data = SelfManySchema().dump(person) assert data["name"] == person.name assert len(data["relatives"]) == len(person.relatives) assert data["relatives"][0]["name"] == person.relatives[0].name assert data["relatives"][0]["age"] == person.relatives[0].age def test_nested_self_list(self): class SelfListSchema(Schema): relatives = fields.List(fields.Nested(lambda: SelfListSchema())) class Meta: additional = ("name", "age") person = User(name="Foo") person.relatives = [User(name="Bar", age=12), User(name="Baz", age=34)] data = SelfListSchema().dump(person) assert data["name"] == person.name assert len(data["relatives"]) == len(person.relatives) assert data["relatives"][0]["name"] == person.relatives[0].name assert data["relatives"][0]["age"] == person.relatives[0].age def test_nested_self_list_string(self): with pytest.warns( DeprecationWarning, match="Passing 'self' to `Nested` is deprecated" ): class SelfListSchema(Schema): relatives = fields.List(fields.Nested("self")) class Meta: additional = ("name", "age") person = User(name="Foo") person.relatives = [User(name="Bar", age=12), User(name="Baz", age=34)] data = SelfListSchema().dump(person) assert data["name"] == person.name assert len(data["relatives"]) == len(person.relatives) assert data["relatives"][0]["name"] == person.relatives[0].name assert data["relatives"][0]["age"] == person.relatives[0].age class RequiredUserSchema(Schema): name = fields.Raw(required=True) def test_serialization_with_required_field(): user = User(name=None) RequiredUserSchema().dump(user) def test_deserialization_with_required_field(): in_data = {} with pytest.raises(ValidationError) as excinfo: RequiredUserSchema().load(in_data) data, errors = excinfo.value.valid_data, excinfo.value.messages assert "name" in errors assert "Missing data for required field." in errors["name"] # field value should also not be in output data assert "name" not in data def test_deserialization_with_required_field_and_custom_validator(): class ValidatingSchema(Schema): color = fields.String( required=True, validate=lambda x: x.lower() == "red" or x.lower() == "blue", error_messages={"validator_failed": "Color must be red or blue"}, ) with pytest.raises(ValidationError) as excinfo: ValidatingSchema().load({"name": "foo"}) errors = excinfo.value.messages assert errors assert "color" in errors assert "Missing data for required field." in errors["color"] with pytest.raises(ValidationError) as excinfo: ValidatingSchema().load({"color": "green"}) errors = excinfo.value.messages assert "color" in errors assert "Color must be red or blue" in errors["color"] class UserContextSchema(Schema): is_owner = fields.Method("get_is_owner") is_collab = fields.Function(lambda user, ctx: user in ctx["blog"]) def get_is_owner(self, user): return self.context["blog"].user.name == user.name class TestContext: def test_context_method(self): owner = User("Joe") blog = Blog(title="Joe Blog", user=owner) context = {"blog": blog} serializer = UserContextSchema() serializer.context = context data = serializer.dump(owner) assert data["is_owner"] is True nonowner = User("Fred") data = serializer.dump(nonowner) assert data["is_owner"] is False def test_context_method_function(self): owner = User("Fred") blog = Blog("Killer Queen", user=owner) collab = User("Brian") blog.collaborators.append(collab) context = {"blog": blog} serializer = UserContextSchema() serializer.context = context data = serializer.dump(collab) assert data["is_collab"] is True noncollab = User("Foo") data = serializer.dump(noncollab) assert data["is_collab"] is False def test_function_field_raises_error_when_context_not_available(self): # only has a function field class UserFunctionContextSchema(Schema): is_collab = fields.Function(lambda user, ctx: user in ctx["blog"]) owner = User("Joe") serializer = UserFunctionContextSchema() # no context serializer.context = None msg = "No context available for Function field {!r}".format("is_collab") with pytest.raises(ValidationError, match=msg): serializer.dump(owner) def test_function_field_handles_bound_serializer(self): class SerializeA: def __call__(self, value): return "value" serialize = SerializeA() # only has a function field class UserFunctionContextSchema(Schema): is_collab = fields.Function(serialize) owner = User("Joe") serializer = UserFunctionContextSchema() # no context serializer.context = None data = serializer.dump(owner) assert data["is_collab"] == "value" def test_fields_context(self): class CSchema(Schema): name = fields.String() ser = CSchema() ser.context["foo"] = 42 assert ser.fields["name"].context == {"foo": 42} def test_nested_fields_inherit_context(self): class InnerSchema(Schema): likes_bikes = fields.Function(lambda obj, ctx: "bikes" in ctx["info"]) class CSchema(Schema): inner = fields.Nested(InnerSchema) ser = CSchema() ser.context["info"] = "i like bikes" obj = {"inner": {}} with pytest.warns(RemovedInMarshmallow4Warning): result = ser.dump(obj) assert result["inner"]["likes_bikes"] is True # Regression test for https://github.com/marshmallow-code/marshmallow/issues/820 def test_nested_list_fields_inherit_context(self): class InnerSchema(Schema): foo = fields.Raw() @validates("foo") def validate_foo(self, value): if "foo_context" not in self.context: raise ValidationError("Missing context") class OuterSchema(Schema): bars = fields.List(fields.Nested(InnerSchema())) inner = InnerSchema() inner.context["foo_context"] = "foo" assert inner.load({"foo": 42}) outer = OuterSchema() outer.context["foo_context"] = "foo" assert outer.load({"bars": [{"foo": 42}]}) # Regression test for https://github.com/marshmallow-code/marshmallow/issues/820 def test_nested_dict_fields_inherit_context(self): class InnerSchema(Schema): foo = fields.Raw() @validates("foo") def validate_foo(self, value): if "foo_context" not in self.context: raise ValidationError("Missing context") class OuterSchema(Schema): bars = fields.Dict(values=fields.Nested(InnerSchema())) inner = InnerSchema() inner.context["foo_context"] = "foo" assert inner.load({"foo": 42}) outer = OuterSchema() outer.context["foo_context"] = "foo" assert outer.load({"bars": {"test": {"foo": 42}}}) # Regression test for https://github.com/marshmallow-code/marshmallow/issues/1404 def test_nested_field_with_unpicklable_object_in_context(self): class Unpicklable: def __deepcopy__(self, _): raise NotImplementedError class InnerSchema(Schema): foo = fields.Raw() with pytest.warns(RemovedInMarshmallow4Warning): class OuterSchema(Schema): inner = fields.Nested(InnerSchema(context={"unp": Unpicklable()})) outer = OuterSchema() obj = {"inner": {"foo": 42}} assert outer.dump(obj) def test_serializer_can_specify_nested_object_as_attribute(blog): class BlogUsernameSchema(Schema): author_name = fields.String(attribute="user.name") ser = BlogUsernameSchema() result = ser.dump(blog) assert result["author_name"] == blog.user.name class TestFieldInheritance: def test_inherit_fields_from_schema_subclass(self): with pytest.warns(ChangedInMarshmallow4Warning): expected = { "field_a": fields.Number(), "field_b": fields.Number(), } class SerializerA(Schema): field_a = expected["field_a"] class SerializerB(SerializerA): field_b = expected["field_b"] assert SerializerB._declared_fields == expected def test_inherit_fields_from_non_schema_subclass(self): with pytest.warns(ChangedInMarshmallow4Warning): expected = { "field_a": fields.Number(), "field_b": fields.Number(), } class PlainBaseClass: field_a = expected["field_a"] class SerializerB1(Schema, PlainBaseClass): field_b = expected["field_b"] class SerializerB2(PlainBaseClass, Schema): field_b = expected["field_b"] assert SerializerB1._declared_fields == expected assert SerializerB2._declared_fields == expected def test_inheritance_follows_mro(self): expected = { "field_a": fields.String(), "field_b": fields.String(), "field_c": fields.String(), "field_d": fields.String(), } # Diamond inheritance graph # MRO: D -> B -> C -> A class SerializerA(Schema): field_a = expected["field_a"] class SerializerB(SerializerA): field_b = expected["field_b"] class SerializerC(SerializerA): field_c = expected["field_c"] class SerializerD(SerializerB, SerializerC): field_d = expected["field_d"] assert SerializerD._declared_fields == expected def get_from_dict(schema, obj, key, default=None): return obj.get("_" + key, default) class TestGetAttribute: def test_get_attribute_is_used(self): class UserDictSchema(Schema): name = fields.Str() email = fields.Email() def get_attribute(self, obj, attr, default): return get_from_dict(self, obj, attr, default) user_dict = {"_name": "joe", "_email": "joe@shmoe.com"} schema = UserDictSchema() result = schema.dump(user_dict) assert result["name"] == user_dict["_name"] assert result["email"] == user_dict["_email"] # can't serialize User object user = User(name="joe", email="joe@shmoe.com") with pytest.raises(AttributeError): schema.dump(user) def test_get_attribute_with_many(self): class UserDictSchema(Schema): name = fields.Str() email = fields.Email() def get_attribute(self, obj, attr, default): return get_from_dict(self, obj, attr, default) user_dicts = [ {"_name": "joe", "_email": "joe@shmoe.com"}, {"_name": "jane", "_email": "jane@shmane.com"}, ] schema = UserDictSchema(many=True) results = schema.dump(user_dicts) for result, user_dict in zip(results, user_dicts): assert result["name"] == user_dict["_name"] assert result["email"] == user_dict["_email"] # can't serialize User object users = [ User(name="joe", email="joe@shmoe.com"), User(name="jane", email="jane@shmane.com"), ] with pytest.raises(AttributeError): schema.dump(users) class TestRequiredFields: class StringSchema(Schema): required_field = fields.Str(required=True) allow_none_field = fields.Str(allow_none=True) allow_none_required_field = fields.Str(required=True, allow_none=True) @pytest.fixture def string_schema(self): return self.StringSchema() @pytest.fixture def data(self): return dict( required_field="foo", allow_none_field="bar", allow_none_required_field="one", ) def test_required_string_field_missing(self, string_schema, data): del data["required_field"] errors = string_schema.validate(data) assert errors["required_field"] == ["Missing data for required field."] def test_required_string_field_failure(self, string_schema, data): data["required_field"] = None errors = string_schema.validate(data) assert errors["required_field"] == ["Field may not be null."] def test_allow_none_param(self, string_schema, data): data["allow_none_field"] = None errors = string_schema.validate(data) assert errors == {} data["allow_none_required_field"] = None string_schema.validate(data) del data["allow_none_required_field"] errors = string_schema.validate(data) assert "allow_none_required_field" in errors def test_allow_none_custom_message(self, data): class MySchema(Schema): allow_none_field = fields.Raw( allow_none=False, error_messages={"null": ""} ) schema = MySchema() errors = schema.validate({"allow_none_field": None}) assert errors["allow_none_field"][0] == "" class TestDefaults: class MySchema(Schema): int_no_default = fields.Int(allow_none=True) str_no_default = fields.Str(allow_none=True) list_no_default = fields.List(fields.Str, allow_none=True) nested_no_default = fields.Nested(UserSchema, many=True, allow_none=True) int_with_default = fields.Int(allow_none=True, dump_default=42) str_with_default = fields.Str(allow_none=True, dump_default="foo") @pytest.fixture def schema(self): return self.MySchema() @pytest.fixture def data(self): return dict( int_no_default=None, str_no_default=None, list_no_default=None, nested_no_default=None, int_with_default=None, str_with_default=None, ) def test_missing_inputs_are_excluded_from_dump_output(self, schema, data): for key in [ "int_no_default", "str_no_default", "list_no_default", "nested_no_default", ]: d = data.copy() del d[key] result = schema.dump(d) # the missing key is not in the serialized result assert key not in result # the rest of the keys are in the result assert all(k in result for k in d) def test_none_is_serialized_to_none(self, schema, data): errors = schema.validate(data) assert errors == {} result = schema.dump(data) for key in data: msg = f"result[{key!r}] should be None" assert result[key] is None, msg def test_default_and_value_missing(self, schema, data): del data["int_with_default"] del data["str_with_default"] result = schema.dump(data) assert result["int_with_default"] == 42 assert result["str_with_default"] == "foo" def test_loading_none(self, schema, data): result = schema.load(data) for key in data: assert result[key] is None def test_missing_inputs_are_excluded_from_load_output(self, schema, data): for key in [ "int_no_default", "str_no_default", "list_no_default", "nested_no_default", ]: d = data.copy() del d[key] result = schema.load(d) # the missing key is not in the deserialized result assert key not in result # the rest of the keys are in the result assert all(k in result for k in d) class TestLoadOnly: class MySchema(Schema): class Meta: load_only = ("str_load_only",) dump_only = ("str_dump_only",) str_dump_only = fields.String() str_load_only = fields.String() str_regular = fields.String() @pytest.fixture def schema(self): return self.MySchema() @pytest.fixture def data(self): return dict( str_dump_only="Dump Only", str_load_only="Load Only", str_regular="Regular String", ) def test_load_only(self, schema, data): result = schema.dump(data) assert "str_load_only" not in result assert "str_dump_only" in result assert "str_regular" in result def test_dump_only(self, schema, data): result = schema.load(data, unknown=EXCLUDE) assert "str_dump_only" not in result assert "str_load_only" in result assert "str_regular" in result # regression test for https://github.com/marshmallow-code/marshmallow/pull/765 def test_url_field_requre_tld_false(self): class NoTldTestSchema(Schema): url = fields.Url(require_tld=False, schemes=["marshmallow"]) schema = NoTldTestSchema() data_with_no_top_level_domain = {"url": "marshmallow://app/discounts"} result = schema.load(data_with_no_top_level_domain) assert result == data_with_no_top_level_domain class TestFromDict: def test_generates_schema(self): MySchema = Schema.from_dict({"foo": fields.Str()}) assert issubclass(MySchema, Schema) def test_name(self): MySchema = Schema.from_dict({"foo": fields.Str()}) assert "GeneratedSchema" in repr(MySchema) SchemaWithName = Schema.from_dict( {"foo": fields.Int()}, name="MyGeneratedSchema" ) assert "MyGeneratedSchema" in repr(SchemaWithName) def test_generated_schemas_are_not_registered(self): n_registry_entries = len(class_registry._registry) Schema.from_dict({"foo": fields.Str()}) Schema.from_dict({"bar": fields.Str()}, name="MyGeneratedSchema") assert len(class_registry._registry) == n_registry_entries with pytest.raises(RegistryError): class_registry.get_class("GeneratedSchema") with pytest.raises(RegistryError): class_registry.get_class("MyGeneratedSchema") def test_meta_options_are_applied(self): class OrderedSchema(Schema): class Meta: ordered = True load_only = ("bar",) OSchema = OrderedSchema.from_dict({"foo": fields.Int(), "bar": fields.Int()}) dumped = OSchema().dump({"foo": 42, "bar": 24}) assert isinstance(dumped, OrderedDict) assert "bar" not in dumped def test_class_registry_returns_schema_type(): class DefinitelyUniqueSchema(Schema): """ Just a schema """ SchemaClass = class_registry.get_class(DefinitelyUniqueSchema.__name__) assert SchemaClass is DefinitelyUniqueSchema @pytest.mark.parametrize("usage_location", ["meta", "init", "load"]) def test_unknown_parameter_value_is_validated(usage_location): class MySchema(Schema): foo = fields.String() with pytest.raises( # noqa: PT012 ValueError, match="Object 'badval' is not a valid value for the 'unknown' parameter", ): # Meta.unknown setting gets caught at class creation time, since that's when # metaclass __new__ runs if usage_location == "meta": class SubSchema(MySchema): class Meta: unknown = "badval" # usages in init and load are caught at call time, as expected elif usage_location == "init": MySchema(unknown="badval") else: MySchema().load({"foo": "bar"}, unknown="badval") @pytest.mark.parametrize("dict_cls", (dict, OrderedDict)) def test_set_dict_class(dict_cls): """Demonstrate how to specify dict_class as class attribute""" class MySchema(Schema): dict_class = dict_cls foo = fields.String() result = MySchema().dump({"foo": "bar"}) assert result == {"foo": "bar"} assert isinstance(result, dict_cls) marshmallow-3.26.1/tests/test_serialization.py000066400000000000000000001150731475016050200215700ustar00rootroot00000000000000"""Tests for field serialization.""" import datetime as dt import decimal import ipaddress import itertools import math import uuid from collections import OrderedDict from typing import NamedTuple import pytest from marshmallow import Schema, fields from marshmallow import missing as missing_ from marshmallow.warnings import ( ChangedInMarshmallow4Warning, RemovedInMarshmallow4Warning, ) from tests.base import ALL_FIELDS, DateEnum, GenderEnum, HairColorEnum, User, central class Point(NamedTuple): x: int y: int class DateTimeList: def __init__(self, dtimes): self.dtimes = dtimes class IntegerList: def __init__(self, ints): self.ints = ints class DateTimeIntegerTuple: def __init__(self, dtime_int): self.dtime_int = dtime_int class TestFieldSerialization: @pytest.fixture def user(self): return User("Foo", email="foo@bar.com", age=42) @pytest.mark.parametrize( ("value", "expected"), [(42, float(42)), (0, float(0)), (None, None)] ) def test_number(self, value, expected, user): with pytest.warns(ChangedInMarshmallow4Warning): field = fields.Number() user.age = value assert field.serialize("age", user) == expected def test_number_as_string(self, user): user.age = 42 with pytest.warns(ChangedInMarshmallow4Warning): field = fields.Number(as_string=True) assert field.serialize("age", user) == str(float(user.age)) def test_number_as_string_passed_none(self, user): user.age = None with pytest.warns(ChangedInMarshmallow4Warning): field = fields.Number(as_string=True, allow_none=True) assert field.serialize("age", user) is None def test_function_field_passed_func(self, user): field = fields.Function(lambda obj: obj.name.upper()) assert field.serialize("key", user) == "FOO" def test_function_field_passed_serialize_only_is_dump_only(self, user): field = fields.Function(serialize=lambda obj: obj.name.upper()) assert field.dump_only is True def test_function_field_passed_deserialize_and_serialize_is_not_dump_only(self): field = fields.Function( serialize=lambda val: val.lower(), deserialize=lambda val: val.upper() ) assert field.dump_only is False def test_function_field_passed_serialize(self, user): field = fields.Function(serialize=lambda obj: obj.name.upper()) assert field.serialize("key", user) == "FOO" # https://github.com/marshmallow-code/marshmallow/issues/395 def test_function_field_does_not_swallow_attribute_error(self, user): def raise_error(obj): raise AttributeError field = fields.Function(serialize=raise_error) with pytest.raises(AttributeError): field.serialize("key", user) def test_serialize_with_load_only_param(self): class AliasingUserSerializer(Schema): name = fields.String() years = fields.Integer(load_only=True) size = fields.Integer(dump_only=True, load_only=True) nicknames = fields.List(fields.Str(), load_only=True) data = { "name": "Mick", "years": "42", "size": "12", "nicknames": ["Your Majesty", "Brenda"], } result = AliasingUserSerializer().dump(data) assert result["name"] == "Mick" assert "years" not in result assert "size" not in result assert "nicknames" not in result def test_function_field_load_only(self): field = fields.Function(deserialize=lambda obj: None) assert field.load_only def test_function_field_passed_serialize_with_context(self, user, monkeypatch): class Parent(Schema): pass field = fields.Function( serialize=lambda obj, context: obj.name.upper() + context["key"] ) with pytest.warns(RemovedInMarshmallow4Warning): field.parent = Parent(context={"key": "BAR"}) assert field.serialize("key", user) == "FOOBAR" def test_function_field_passed_uncallable_object(self): with pytest.raises(TypeError): fields.Function("uncallable") def test_integer_field(self, user): field = fields.Integer() assert field.serialize("age", user) == 42 def test_integer_as_string_field(self, user): field = fields.Integer(as_string=True) assert field.serialize("age", user) == "42" def test_integer_field_default(self, user): user.age = None field = fields.Integer(dump_default=0) assert field.serialize("age", user) is None # missing assert field.serialize("age", {}) == 0 def test_integer_field_default_set_to_none(self, user): user.age = None field = fields.Integer(dump_default=None) assert field.serialize("age", user) is None def test_uuid_field(self, user): user.uuid1 = uuid.UUID("12345678123456781234567812345678") user.uuid2 = None field = fields.UUID() assert isinstance(field.serialize("uuid1", user), str) assert field.serialize("uuid1", user) == "12345678-1234-5678-1234-567812345678" assert field.serialize("uuid2", user) is None def test_ip_address_field(self, user): ipv4_string = "192.168.0.1" ipv6_string = "ffff::ffff" ipv6_exploded_string = ipaddress.ip_address("ffff::ffff").exploded user.ipv4 = ipaddress.ip_address(ipv4_string) user.ipv6 = ipaddress.ip_address(ipv6_string) user.empty_ip = None field_compressed = fields.IP() assert isinstance(field_compressed.serialize("ipv4", user), str) assert field_compressed.serialize("ipv4", user) == ipv4_string assert isinstance(field_compressed.serialize("ipv6", user), str) assert field_compressed.serialize("ipv6", user) == ipv6_string assert field_compressed.serialize("empty_ip", user) is None field_exploded = fields.IP(exploded=True) assert isinstance(field_exploded.serialize("ipv6", user), str) assert field_exploded.serialize("ipv6", user) == ipv6_exploded_string def test_ipv4_address_field(self, user): ipv4_string = "192.168.0.1" user.ipv4 = ipaddress.ip_address(ipv4_string) user.empty_ip = None field = fields.IPv4() assert isinstance(field.serialize("ipv4", user), str) assert field.serialize("ipv4", user) == ipv4_string assert field.serialize("empty_ip", user) is None def test_ipv6_address_field(self, user): ipv6_string = "ffff::ffff" ipv6_exploded_string = ipaddress.ip_address("ffff::ffff").exploded user.ipv6 = ipaddress.ip_address(ipv6_string) user.empty_ip = None field_compressed = fields.IPv6() assert isinstance(field_compressed.serialize("ipv6", user), str) assert field_compressed.serialize("ipv6", user) == ipv6_string assert field_compressed.serialize("empty_ip", user) is None field_exploded = fields.IPv6(exploded=True) assert isinstance(field_exploded.serialize("ipv6", user), str) assert field_exploded.serialize("ipv6", user) == ipv6_exploded_string def test_ip_interface_field(self, user): ipv4interface_string = "192.168.0.1/24" ipv6interface_string = "ffff::ffff/128" ipv6interface_exploded_string = ipaddress.ip_interface( "ffff::ffff/128" ).exploded user.ipv4interface = ipaddress.ip_interface(ipv4interface_string) user.ipv6interface = ipaddress.ip_interface(ipv6interface_string) user.empty_ipinterface = None field_compressed = fields.IPInterface() assert isinstance(field_compressed.serialize("ipv4interface", user), str) assert field_compressed.serialize("ipv4interface", user) == ipv4interface_string assert isinstance(field_compressed.serialize("ipv6interface", user), str) assert field_compressed.serialize("ipv6interface", user) == ipv6interface_string assert field_compressed.serialize("empty_ipinterface", user) is None field_exploded = fields.IPInterface(exploded=True) assert isinstance(field_exploded.serialize("ipv6interface", user), str) assert ( field_exploded.serialize("ipv6interface", user) == ipv6interface_exploded_string ) def test_ipv4_interface_field(self, user): ipv4interface_string = "192.168.0.1/24" user.ipv4interface = ipaddress.ip_interface(ipv4interface_string) user.empty_ipinterface = None field = fields.IPv4Interface() assert isinstance(field.serialize("ipv4interface", user), str) assert field.serialize("ipv4interface", user) == ipv4interface_string assert field.serialize("empty_ipinterface", user) is None def test_ipv6_interface_field(self, user): ipv6interface_string = "ffff::ffff/128" ipv6interface_exploded_string = ipaddress.ip_interface( "ffff::ffff/128" ).exploded user.ipv6interface = ipaddress.ip_interface(ipv6interface_string) user.empty_ipinterface = None field_compressed = fields.IPv6Interface() assert isinstance(field_compressed.serialize("ipv6interface", user), str) assert field_compressed.serialize("ipv6interface", user) == ipv6interface_string assert field_compressed.serialize("empty_ipinterface", user) is None field_exploded = fields.IPv6Interface(exploded=True) assert isinstance(field_exploded.serialize("ipv6interface", user), str) assert ( field_exploded.serialize("ipv6interface", user) == ipv6interface_exploded_string ) def test_enum_field_by_symbol_serialization(self, user): user.sex = GenderEnum.male field = fields.Enum(GenderEnum) assert field.serialize("sex", user) == "male" def test_enum_field_by_value_true_serialization(self, user): user.hair_color = HairColorEnum.black field = fields.Enum(HairColorEnum, by_value=True) assert field.serialize("hair_color", user) == "black hair" user.sex = GenderEnum.male field = fields.Enum(GenderEnum, by_value=True) assert field.serialize("sex", user) == 1 user.some_date = DateEnum.date_1 def test_enum_field_by_value_field_serialization(self, user): user.hair_color = HairColorEnum.black field = fields.Enum(HairColorEnum, by_value=fields.String) assert field.serialize("hair_color", user) == "black hair" user.sex = GenderEnum.male field = fields.Enum(GenderEnum, by_value=fields.Integer) assert field.serialize("sex", user) == 1 user.some_date = DateEnum.date_1 field = fields.Enum(DateEnum, by_value=fields.Date(format="%d/%m/%Y")) assert field.serialize("some_date", user) == "29/02/2004" def test_decimal_field(self, user): user.m1 = 12 user.m2 = "12.355" user.m3 = decimal.Decimal(1) user.m4 = None field = fields.Decimal() assert isinstance(field.serialize("m1", user), decimal.Decimal) assert field.serialize("m1", user) == decimal.Decimal(12) assert isinstance(field.serialize("m2", user), decimal.Decimal) assert field.serialize("m2", user) == decimal.Decimal("12.355") assert isinstance(field.serialize("m3", user), decimal.Decimal) assert field.serialize("m3", user) == decimal.Decimal(1) assert field.serialize("m4", user) is None field = fields.Decimal(1) assert isinstance(field.serialize("m1", user), decimal.Decimal) assert field.serialize("m1", user) == decimal.Decimal(12) assert isinstance(field.serialize("m2", user), decimal.Decimal) assert field.serialize("m2", user) == decimal.Decimal("12.4") assert isinstance(field.serialize("m3", user), decimal.Decimal) assert field.serialize("m3", user) == decimal.Decimal(1) assert field.serialize("m4", user) is None field = fields.Decimal(1, decimal.ROUND_DOWN) assert isinstance(field.serialize("m1", user), decimal.Decimal) assert field.serialize("m1", user) == decimal.Decimal(12) assert isinstance(field.serialize("m2", user), decimal.Decimal) assert field.serialize("m2", user) == decimal.Decimal("12.3") assert isinstance(field.serialize("m3", user), decimal.Decimal) assert field.serialize("m3", user) == decimal.Decimal(1) assert field.serialize("m4", user) is None def test_decimal_field_string(self, user): user.m1 = 12 user.m2 = "12.355" user.m3 = decimal.Decimal(1) user.m4 = None field = fields.Decimal(as_string=True) assert isinstance(field.serialize("m1", user), str) assert field.serialize("m1", user) == "12" assert isinstance(field.serialize("m2", user), str) assert field.serialize("m2", user) == "12.355" assert isinstance(field.serialize("m3", user), str) assert field.serialize("m3", user) == "1" assert field.serialize("m4", user) is None field = fields.Decimal(1, as_string=True) assert isinstance(field.serialize("m1", user), str) assert field.serialize("m1", user) == "12.0" assert isinstance(field.serialize("m2", user), str) assert field.serialize("m2", user) == "12.4" assert isinstance(field.serialize("m3", user), str) assert field.serialize("m3", user) == "1.0" assert field.serialize("m4", user) is None field = fields.Decimal(1, decimal.ROUND_DOWN, as_string=True) assert isinstance(field.serialize("m1", user), str) assert field.serialize("m1", user) == "12.0" assert isinstance(field.serialize("m2", user), str) assert field.serialize("m2", user) == "12.3" assert isinstance(field.serialize("m3", user), str) assert field.serialize("m3", user) == "1.0" assert field.serialize("m4", user) is None def test_decimal_field_special_values(self, user): user.m1 = "-NaN" user.m2 = "NaN" user.m3 = "-sNaN" user.m4 = "sNaN" user.m5 = "-Infinity" user.m6 = "Infinity" user.m7 = "-0" field = fields.Decimal(places=2, allow_nan=True) m1s = field.serialize("m1", user) assert isinstance(m1s, decimal.Decimal) assert m1s.is_qnan() assert not m1s.is_signed() m2s = field.serialize("m2", user) assert isinstance(m2s, decimal.Decimal) assert m2s.is_qnan() assert not m2s.is_signed() m3s = field.serialize("m3", user) assert isinstance(m3s, decimal.Decimal) assert m3s.is_qnan() assert not m3s.is_signed() m4s = field.serialize("m4", user) assert isinstance(m4s, decimal.Decimal) assert m4s.is_qnan() assert not m4s.is_signed() m5s = field.serialize("m5", user) assert isinstance(m5s, decimal.Decimal) assert m5s.is_infinite() assert m5s.is_signed() m6s = field.serialize("m6", user) assert isinstance(m6s, decimal.Decimal) assert m6s.is_infinite() assert not m6s.is_signed() m7s = field.serialize("m7", user) assert isinstance(m7s, decimal.Decimal) assert m7s.is_zero() assert m7s.is_signed() field = fields.Decimal(as_string=True, allow_nan=True) m2s = field.serialize("m2", user) assert isinstance(m2s, str) assert m2s == user.m2 m5s = field.serialize("m5", user) assert isinstance(m5s, str) assert m5s == user.m5 m6s = field.serialize("m6", user) assert isinstance(m6s, str) assert m6s == user.m6 def test_decimal_field_special_values_not_permitted(self, user): user.m7 = "-0" field = fields.Decimal(places=2) m7s = field.serialize("m7", user) assert isinstance(m7s, decimal.Decimal) assert m7s.is_zero() assert m7s.is_signed() def test_decimal_field_fixed_point_representation(self, user): """ Test we get fixed-point string representation for a Decimal number that would normally output in engineering notation. """ user.m1 = "0.00000000100000000" field = fields.Decimal() s = field.serialize("m1", user) assert isinstance(s, decimal.Decimal) assert s == decimal.Decimal("1.00000000E-9") field = fields.Decimal(as_string=True) s = field.serialize("m1", user) assert isinstance(s, str) assert s == user.m1 field = fields.Decimal(as_string=True, places=2) s = field.serialize("m1", user) assert isinstance(s, str) assert s == "0.00" def test_boolean_field_serialization(self, user): field = fields.Boolean() user.truthy = "non-falsy-ish" user.falsy = "false" user.none = None assert field.serialize("truthy", user) is True assert field.serialize("falsy", user) is False assert field.serialize("none", user) is None def test_email_field_serialize_none(self, user): user.email = None field = fields.Email() assert field.serialize("email", user) is None def test_dict_field_serialize_none(self, user): user.various_data = None field = fields.Dict() assert field.serialize("various_data", user) is None def test_dict_field_serialize(self, user): user.various_data = {"foo": "bar"} field = fields.Dict() dump = field.serialize("various_data", user) assert dump == {"foo": "bar"} # Check dump is a distinct object dump["foo"] = "baz" assert user.various_data["foo"] == "bar" def test_dict_field_serialize_ordereddict(self, user): user.various_data = OrderedDict([("foo", "bar"), ("bar", "baz")]) field = fields.Dict() assert field.serialize("various_data", user) == OrderedDict( [("foo", "bar"), ("bar", "baz")] ) def test_structured_dict_value_serialize(self, user): user.various_data = {"foo": decimal.Decimal("1")} field = fields.Dict(values=fields.Decimal) assert field.serialize("various_data", user) == {"foo": 1} def test_structured_dict_key_serialize(self, user): user.various_data = {1: "bar"} field = fields.Dict(keys=fields.Str) assert field.serialize("various_data", user) == {"1": "bar"} def test_structured_dict_key_value_serialize(self, user): user.various_data = {1: decimal.Decimal("1")} field = fields.Dict(keys=fields.Str, values=fields.Decimal) assert field.serialize("various_data", user) == {"1": 1} def test_url_field_serialize_none(self, user): user.homepage = None field = fields.Url() assert field.serialize("homepage", user) is None def test_method_field_with_method_missing(self): class BadSerializer(Schema): bad_field = fields.Method("invalid") with pytest.raises(AttributeError): BadSerializer() def test_method_field_passed_serialize_only_is_dump_only(self, user): field = fields.Method(serialize="method") assert field.dump_only is True assert field.load_only is False def test_method_field_passed_deserialize_only_is_load_only(self): field = fields.Method(deserialize="somemethod") assert field.load_only is True assert field.dump_only is False def test_method_field_with_uncallable_attribute(self): class BadSerializer(Schema): foo = "not callable" bad_field = fields.Method("foo") with pytest.raises(TypeError): BadSerializer() # https://github.com/marshmallow-code/marshmallow/issues/395 def test_method_field_does_not_swallow_attribute_error(self): class MySchema(Schema): mfield = fields.Method("raise_error") def raise_error(self, obj): raise AttributeError with pytest.raises(AttributeError): MySchema().dump({}) def test_method_with_no_serialize_is_missing(self): m = fields.Method() m.parent = Schema() assert m.serialize("", "", "") is missing_ def test_serialize_with_data_key_param(self): class DumpToSchema(Schema): name = fields.String(data_key="NamE") years = fields.Integer(data_key="YearS") data = {"name": "Richard", "years": 11} result = DumpToSchema().dump(data) assert result == {"NamE": "Richard", "YearS": 11} def test_serialize_with_data_key_as_empty_string(self): class MySchema(Schema): name = fields.Raw(data_key="") schema = MySchema() assert schema.dump({"name": "Grace"}) == {"": "Grace"} def test_serialize_with_attribute_and_data_key_uses_data_key(self): class ConfusedDumpToAndAttributeSerializer(Schema): name = fields.String(data_key="FullName") username = fields.String(attribute="uname", data_key="UserName") years = fields.Integer(attribute="le_wild_age", data_key="Years") data = {"name": "Mick", "uname": "mick_the_awesome", "le_wild_age": 999} result = ConfusedDumpToAndAttributeSerializer().dump(data) assert result == { "FullName": "Mick", "UserName": "mick_the_awesome", "Years": 999, } @pytest.mark.parametrize("fmt", ["rfc", "rfc822"]) @pytest.mark.parametrize( ("value", "expected"), [ (dt.datetime(2013, 11, 10, 1, 23, 45), "Sun, 10 Nov 2013 01:23:45 -0000"), ( dt.datetime(2013, 11, 10, 1, 23, 45, tzinfo=dt.timezone.utc), "Sun, 10 Nov 2013 01:23:45 +0000", ), ( dt.datetime(2013, 11, 10, 1, 23, 45, tzinfo=central), "Sun, 10 Nov 2013 01:23:45 -0600", ), ], ) def test_datetime_field_rfc822(self, fmt, value, expected): field = fields.DateTime(format=fmt) assert field.serialize("d", {"d": value}) == expected @pytest.mark.parametrize( ("fmt", "value", "expected"), [ ("timestamp", dt.datetime(1970, 1, 1), 0), ("timestamp", dt.datetime(2013, 11, 10, 0, 23, 45), 1384043025), ( "timestamp", dt.datetime(2013, 11, 10, 0, 23, 45, tzinfo=dt.timezone.utc), 1384043025, ), ( "timestamp", dt.datetime(2013, 11, 10, 0, 23, 45, tzinfo=central), 1384064625, ), ("timestamp_ms", dt.datetime(2013, 11, 10, 0, 23, 45), 1384043025000), ( "timestamp_ms", dt.datetime(2013, 11, 10, 0, 23, 45, tzinfo=dt.timezone.utc), 1384043025000, ), ( "timestamp_ms", dt.datetime(2013, 11, 10, 0, 23, 45, tzinfo=central), 1384064625000, ), ], ) def test_datetime_field_timestamp(self, fmt, value, expected): field = fields.DateTime(format=fmt) assert field.serialize("d", {"d": value}) == expected @pytest.mark.parametrize("fmt", ["iso", "iso8601", None]) @pytest.mark.parametrize( ("value", "expected"), [ (dt.datetime(2013, 11, 10, 1, 23, 45), "2013-11-10T01:23:45"), ( dt.datetime(2013, 11, 10, 1, 23, 45, 123456, tzinfo=dt.timezone.utc), "2013-11-10T01:23:45.123456+00:00", ), ( dt.datetime(2013, 11, 10, 1, 23, 45, tzinfo=dt.timezone.utc), "2013-11-10T01:23:45+00:00", ), ( dt.datetime(2013, 11, 10, 1, 23, 45, tzinfo=central), "2013-11-10T01:23:45-06:00", ), ], ) def test_datetime_field_iso8601(self, fmt, value, expected): if fmt is None: # Test default is ISO field = fields.DateTime() else: field = fields.DateTime(format=fmt) assert field.serialize("d", {"d": value}) == expected def test_datetime_field_format(self, user): datetimeformat = "%Y-%m-%d" field = fields.DateTime(format=datetimeformat) assert field.serialize("created", user) == user.created.strftime(datetimeformat) def test_string_field(self): field = fields.String() user = User(name=b"foo") assert field.serialize("name", user) == "foo" field = fields.String(allow_none=True) user.name = None assert field.serialize("name", user) is None def test_string_field_default_to_empty_string(self, user): field = fields.String(dump_default="") assert field.serialize("notfound", {}) == "" def test_time_field(self, user): field = fields.Time() expected = user.time_registered.isoformat()[:15] assert field.serialize("time_registered", user) == expected user.time_registered = None assert field.serialize("time_registered", user) is None @pytest.mark.parametrize("fmt", ["iso", "iso8601", None]) @pytest.mark.parametrize( ("value", "expected"), [ (dt.time(1, 23, 45), "01:23:45"), (dt.time(1, 23, 45, 123000), "01:23:45.123000"), (dt.time(1, 23, 45, 123456), "01:23:45.123456"), ], ) def test_time_field_iso8601(self, fmt, value, expected): if fmt is None: # Test default is ISO field = fields.Time() else: field = fields.Time(format=fmt) assert field.serialize("d", {"d": value}) == expected def test_time_field_format(self, user): fmt = "%H:%M:%S" field = fields.Time(format=fmt) assert field.serialize("birthtime", user) == user.birthtime.strftime(fmt) def test_date_field(self, user): field = fields.Date() assert field.serialize("birthdate", user) == user.birthdate.isoformat() user.birthdate = None assert field.serialize("birthdate", user) is None def test_timedelta_field(self, user): user.d1 = dt.timedelta(days=1, seconds=1, microseconds=1) user.d2 = dt.timedelta(days=0, seconds=86401, microseconds=1) user.d3 = dt.timedelta(days=0, seconds=0, microseconds=86401000001) user.d4 = dt.timedelta(days=0, seconds=0, microseconds=0) user.d5 = dt.timedelta(days=-1, seconds=0, microseconds=0) user.d6 = dt.timedelta( days=1, seconds=1, microseconds=1, milliseconds=1, minutes=1, hours=1, weeks=1, ) field = fields.TimeDelta(fields.TimeDelta.DAYS) assert field.serialize("d1", user) == 1 field = fields.TimeDelta(fields.TimeDelta.SECONDS) assert field.serialize("d1", user) == 86401 field = fields.TimeDelta(fields.TimeDelta.MICROSECONDS) assert field.serialize("d1", user) == 86401000001 field = fields.TimeDelta(fields.TimeDelta.HOURS) assert field.serialize("d1", user) == 24 field = fields.TimeDelta(fields.TimeDelta.DAYS) assert field.serialize("d2", user) == 1 field = fields.TimeDelta(fields.TimeDelta.SECONDS) assert field.serialize("d2", user) == 86401 field = fields.TimeDelta(fields.TimeDelta.MICROSECONDS) assert field.serialize("d2", user) == 86401000001 field = fields.TimeDelta(fields.TimeDelta.DAYS) assert field.serialize("d3", user) == 1 field = fields.TimeDelta(fields.TimeDelta.SECONDS) assert field.serialize("d3", user) == 86401 field = fields.TimeDelta(fields.TimeDelta.MICROSECONDS) assert field.serialize("d3", user) == 86401000001 field = fields.TimeDelta(fields.TimeDelta.DAYS) assert field.serialize("d4", user) == 0 field = fields.TimeDelta(fields.TimeDelta.SECONDS) assert field.serialize("d4", user) == 0 field = fields.TimeDelta(fields.TimeDelta.MICROSECONDS) assert field.serialize("d4", user) == 0 field = fields.TimeDelta(fields.TimeDelta.DAYS) assert field.serialize("d5", user) == -1 field = fields.TimeDelta(fields.TimeDelta.SECONDS) assert field.serialize("d5", user) == -86400 field = fields.TimeDelta(fields.TimeDelta.MICROSECONDS) assert field.serialize("d5", user) == -86400000000 field = fields.TimeDelta(fields.TimeDelta.WEEKS) assert field.serialize("d6", user) == 1 field = fields.TimeDelta(fields.TimeDelta.DAYS) assert field.serialize("d6", user) == 7 + 1 field = fields.TimeDelta(fields.TimeDelta.HOURS) assert field.serialize("d6", user) == 7 * 24 + 24 + 1 field = fields.TimeDelta(fields.TimeDelta.MINUTES) assert field.serialize("d6", user) == 7 * 24 * 60 + 24 * 60 + 60 + 1 d6_seconds = ( 7 * 24 * 60 * 60 + 24 * 60 * 60 # 1 week + 60 * 60 # 1 day + 60 # 1 hour + 1 # 1 minute ) field = fields.TimeDelta(fields.TimeDelta.SECONDS) assert field.serialize("d6", user) == d6_seconds field = fields.TimeDelta(fields.TimeDelta.MILLISECONDS) assert field.serialize("d6", user) == d6_seconds * 1000 + 1 field = fields.TimeDelta(fields.TimeDelta.MICROSECONDS) assert field.serialize("d6", user) == d6_seconds * 10**6 + 1000 + 1 user.d7 = None assert field.serialize("d7", user) is None # https://github.com/marshmallow-code/marshmallow/issues/1856 user.d8 = dt.timedelta(milliseconds=345) field = fields.TimeDelta(fields.TimeDelta.MILLISECONDS) assert field.serialize("d8", user) == 345 user.d9 = dt.timedelta(milliseconds=1999) field = fields.TimeDelta(fields.TimeDelta.SECONDS) assert field.serialize("d9", user) == 1 user.d10 = dt.timedelta( weeks=1, days=6, hours=2, minutes=5, seconds=51, milliseconds=10, microseconds=742, ) field = fields.TimeDelta(fields.TimeDelta.MICROSECONDS, float) unit_value = dt.timedelta(microseconds=1).total_seconds() assert math.isclose( field.serialize("d10", user), user.d10.total_seconds() / unit_value ) field = fields.TimeDelta(fields.TimeDelta.MILLISECONDS, float) unit_value = dt.timedelta(milliseconds=1).total_seconds() assert math.isclose( field.serialize("d10", user), user.d10.total_seconds() / unit_value ) field = fields.TimeDelta(fields.TimeDelta.SECONDS, float) assert math.isclose(field.serialize("d10", user), user.d10.total_seconds()) field = fields.TimeDelta(fields.TimeDelta.MINUTES, float) unit_value = dt.timedelta(minutes=1).total_seconds() assert math.isclose( field.serialize("d10", user), user.d10.total_seconds() / unit_value ) field = fields.TimeDelta(fields.TimeDelta.HOURS, float) unit_value = dt.timedelta(hours=1).total_seconds() assert math.isclose( field.serialize("d10", user), user.d10.total_seconds() / unit_value ) field = fields.TimeDelta(fields.TimeDelta.DAYS, float) unit_value = dt.timedelta(days=1).total_seconds() assert math.isclose( field.serialize("d10", user), user.d10.total_seconds() / unit_value ) field = fields.TimeDelta(fields.TimeDelta.WEEKS, float) unit_value = dt.timedelta(weeks=1).total_seconds() assert math.isclose( field.serialize("d10", user), user.d10.total_seconds() / unit_value ) with pytest.raises(ValueError): fields.TimeDelta(fields.TimeDelta.SECONDS, str) def test_datetime_list_field(self): obj = DateTimeList([dt.datetime.now(dt.timezone.utc), dt.datetime.now()]) field = fields.List(fields.DateTime) result = field.serialize("dtimes", obj) assert all(type(each) is str for each in result) def test_list_field_serialize_none_returns_none(self): obj = DateTimeList(None) field = fields.List(fields.DateTime) assert field.serialize("dtimes", obj) is None def test_list_field_work_with_generator_single_value(self): def custom_generator(): yield dt.datetime.now(dt.timezone.utc) obj = DateTimeList(custom_generator()) field = fields.List(fields.DateTime) result = field.serialize("dtimes", obj) assert len(result) == 1 def test_list_field_work_with_generators_multiple_values(self): def custom_generator(): yield from [dt.datetime.now(dt.timezone.utc), dt.datetime.now()] obj = DateTimeList(custom_generator()) field = fields.List(fields.DateTime) result = field.serialize("dtimes", obj) assert len(result) == 2 def test_list_field_work_with_generators_empty_generator_returns_none_for_every_non_returning_yield_statement( self, ): def custom_generator(): yield yield obj = DateTimeList(custom_generator()) field = fields.List(fields.DateTime, allow_none=True) result = field.serialize("dtimes", obj) assert len(result) == 2 assert result[0] is None assert result[1] is None def test_list_field_work_with_set(self): custom_set = {1, 2, 3} obj = IntegerList(custom_set) field = fields.List(fields.Int) result = field.serialize("ints", obj) assert len(result) == 3 assert 1 in result assert 2 in result assert 3 in result def test_list_field_work_with_custom_class_with_iterator_protocol(self): class IteratorSupportingClass: def __init__(self, iterable): self.iterable = iterable def __iter__(self): return iter(self.iterable) ints = IteratorSupportingClass([1, 2, 3]) obj = IntegerList(ints) field = fields.List(fields.Int) result = field.serialize("ints", obj) assert len(result) == 3 assert result[0] == 1 assert result[1] == 2 assert result[2] == 3 def test_bad_list_field(self): class ASchema(Schema): id = fields.Int() with pytest.raises(ValueError): fields.List("string") expected_msg = ( "The list elements must be a subclass or instance of " "marshmallow.base.FieldABC" ) with pytest.raises(ValueError, match=expected_msg): fields.List(ASchema) def test_datetime_integer_tuple_field(self): obj = DateTimeIntegerTuple((dt.datetime.now(dt.timezone.utc), 42)) field = fields.Tuple([fields.DateTime, fields.Integer]) result = field.serialize("dtime_int", obj) assert type(result[0]) is str assert type(result[1]) is int def test_tuple_field_serialize_none_returns_none(self): obj = DateTimeIntegerTuple(None) field = fields.Tuple([fields.DateTime, fields.Integer]) assert field.serialize("dtime_int", obj) is None def test_bad_tuple_field(self): class ASchema(Schema): id = fields.Int() with pytest.raises(ValueError): fields.Tuple(["string"]) with pytest.raises(ValueError): fields.Tuple(fields.String) expected_msg = ( 'Elements of "tuple_fields" must be subclasses or ' "instances of marshmallow.base.FieldABC." ) with pytest.raises(ValueError, match=expected_msg): fields.Tuple([ASchema]) def test_serialize_does_not_apply_validators(self, user): field = fields.Raw(validate=lambda x: False) # No validation error raised assert field.serialize("age", user) == user.age def test_constant_field_serialization(self, user): field = fields.Constant("something") assert field.serialize("whatever", user) == "something" def test_constant_is_always_included_in_serialized_data(self): class MySchema(Schema): foo = fields.Constant(42) sch = MySchema() assert sch.dump({"bar": 24})["foo"] == 42 assert sch.dump({"foo": 24})["foo"] == 42 def test_constant_field_serialize_when_omitted(self): class MiniUserSchema(Schema): name = fields.Constant("bill") s = MiniUserSchema() assert s.dump({})["name"] == "bill" @pytest.mark.parametrize("FieldClass", ALL_FIELDS) def test_all_fields_serialize_none_to_none(self, FieldClass): field = FieldClass(allow_none=True) res = field.serialize("foo", {"foo": None}) assert res is None class TestSchemaSerialization: def test_serialize_with_missing_param_value(self): class AliasingUserSerializer(Schema): name = fields.String() birthdate = fields.DateTime(dump_default=dt.datetime(2017, 9, 29)) data = {"name": "Mick"} result = AliasingUserSerializer().dump(data) assert result["name"] == "Mick" assert result["birthdate"] == "2017-09-29T00:00:00" def test_serialize_with_missing_param_callable(self): class AliasingUserSerializer(Schema): name = fields.String() birthdate = fields.DateTime(dump_default=lambda: dt.datetime(2017, 9, 29)) data = {"name": "Mick"} result = AliasingUserSerializer().dump(data) assert result["name"] == "Mick" assert result["birthdate"] == "2017-09-29T00:00:00" def test_serializing_named_tuple(): field = fields.Raw() p = Point(x=4, y=2) assert field.serialize("x", p) == 4 def test_serializing_named_tuple_with_meta(): p = Point(x=4, y=2) class PointSerializer(Schema): class Meta: fields = ("x", "y") serialized = PointSerializer().dump(p) assert serialized["x"] == 4 assert serialized["y"] == 2 def test_serializing_slice(): values = [{"value": value} for value in range(5)] sliced = itertools.islice(values, None) class ValueSchema(Schema): value = fields.Int() serialized = ValueSchema(many=True).dump(sliced) assert serialized == values # https://github.com/marshmallow-code/marshmallow/issues/1163 def test_nested_field_many_serializing_generator(): class MySchema(Schema): name = fields.Str() class OtherSchema(Schema): objects = fields.Nested(MySchema, many=True) def gen(): yield {"name": "foo"} yield {"name": "bar"} obj = {"objects": gen()} data = OtherSchema().dump(obj) assert data.get("objects") == [{"name": "foo"}, {"name": "bar"}] marshmallow-3.26.1/tests/test_utils.py000066400000000000000000000170101475016050200200430ustar00rootroot00000000000000import datetime as dt from copy import copy, deepcopy from functools import partial from typing import NamedTuple import pytest from marshmallow import Schema, fields, utils from tests.base import assert_date_equal, assert_time_equal, central def test_missing_singleton_copy(): assert copy(utils.missing) is utils.missing assert deepcopy(utils.missing) is utils.missing class PointNT(NamedTuple): x: int y: int class PointClass: def __init__(self, x, y): self.x = x self.y = y class PointDict(dict): def __init__(self, x, y): super().__init__({"x": x}) self.y = y @pytest.mark.parametrize( "obj", [PointNT(24, 42), PointClass(24, 42), PointDict(24, 42), {"x": 24, "y": 42}] ) def test_get_value_from_object(obj): assert utils.get_value(obj, "x") == 24 assert utils.get_value(obj, "y") == 42 def test_get_value_from_namedtuple_with_default(): p = PointNT(x=42, y=None) # Default is only returned if key is not found assert utils.get_value(p, "z", default=123) == 123 # since 'y' is an attribute, None is returned instead of the default assert utils.get_value(p, "y", default=123) is None class Triangle: def __init__(self, p1, p2, p3): self.p1 = p1 self.p2 = p2 self.p3 = p3 self.points = [p1, p2, p3] def test_get_value_for_nested_object(): tri = Triangle(p1=PointClass(1, 2), p2=PointNT(3, 4), p3={"x": 5, "y": 6}) assert utils.get_value(tri, "p1.x") == 1 assert utils.get_value(tri, "p2.x") == 3 assert utils.get_value(tri, "p3.x") == 5 # regression test for https://github.com/marshmallow-code/marshmallow/issues/62 def test_get_value_from_dict(): d = dict(items=["foo", "bar"], keys=["baz", "quux"]) assert utils.get_value(d, "items") == ["foo", "bar"] assert utils.get_value(d, "keys") == ["baz", "quux"] def test_get_value(): lst = [1, 2, 3] assert utils.get_value(lst, 1) == 2 class MyInt(int): pass assert utils.get_value(lst, MyInt(1)) == 2 def test_set_value(): d = {} utils.set_value(d, "foo", 42) assert d == {"foo": 42} d = {} utils.set_value(d, "foo.bar", 42) assert d == {"foo": {"bar": 42}} d = {"foo": {}} utils.set_value(d, "foo.bar", 42) assert d == {"foo": {"bar": 42}} d = {"foo": 42} with pytest.raises(ValueError): utils.set_value(d, "foo.bar", 42) def test_is_keyed_tuple(): p = PointNT(24, 42) assert utils.is_keyed_tuple(p) is True t = (24, 42) assert utils.is_keyed_tuple(t) is False d = {"x": 42, "y": 24} assert utils.is_keyed_tuple(d) is False s = "xy" assert utils.is_keyed_tuple(s) is False lst = [24, 42] assert utils.is_keyed_tuple(lst) is False def test_is_collection(): assert utils.is_collection([1, "foo", {}]) is True assert utils.is_collection(("foo", 2.3)) is True assert utils.is_collection({"foo": "bar"}) is False @pytest.mark.parametrize( ("value", "expected"), [ (dt.datetime(2013, 11, 10, 1, 23, 45), "Sun, 10 Nov 2013 01:23:45 -0000"), ( dt.datetime(2013, 11, 10, 1, 23, 45, tzinfo=dt.timezone.utc), "Sun, 10 Nov 2013 01:23:45 +0000", ), ( dt.datetime(2013, 11, 10, 1, 23, 45, tzinfo=central), "Sun, 10 Nov 2013 01:23:45 -0600", ), ], ) def test_rfc_format(value, expected): assert utils.rfcformat(value) == expected @pytest.mark.parametrize( ("value", "expected"), [ (dt.datetime(2013, 11, 10, 1, 23, 45), "2013-11-10T01:23:45"), ( dt.datetime(2013, 11, 10, 1, 23, 45, 123456, tzinfo=dt.timezone.utc), "2013-11-10T01:23:45.123456+00:00", ), ( dt.datetime(2013, 11, 10, 1, 23, 45, tzinfo=dt.timezone.utc), "2013-11-10T01:23:45+00:00", ), ( dt.datetime(2013, 11, 10, 1, 23, 45, tzinfo=central), "2013-11-10T01:23:45-06:00", ), ], ) def test_isoformat(value, expected): assert utils.isoformat(value) == expected @pytest.mark.parametrize( ("value", "expected"), [ ("Sun, 10 Nov 2013 01:23:45 -0000", dt.datetime(2013, 11, 10, 1, 23, 45)), ( "Sun, 10 Nov 2013 01:23:45 +0000", dt.datetime(2013, 11, 10, 1, 23, 45, tzinfo=dt.timezone.utc), ), ( "Sun, 10 Nov 2013 01:23:45 -0600", dt.datetime(2013, 11, 10, 1, 23, 45, tzinfo=central), ), ], ) def test_from_rfc(value, expected): result = utils.from_rfc(value) assert type(result) is dt.datetime assert result == expected @pytest.mark.parametrize( ("value", "expected"), [ ("2013-11-10T01:23:45", dt.datetime(2013, 11, 10, 1, 23, 45)), ( "2013-11-10T01:23:45+00:00", dt.datetime(2013, 11, 10, 1, 23, 45, tzinfo=dt.timezone.utc), ), ( # Regression test for https://github.com/marshmallow-code/marshmallow/issues/1251 "2013-11-10T01:23:45.123+00:00", dt.datetime(2013, 11, 10, 1, 23, 45, 123000, tzinfo=dt.timezone.utc), ), ( "2013-11-10T01:23:45.123456+00:00", dt.datetime(2013, 11, 10, 1, 23, 45, 123456, tzinfo=dt.timezone.utc), ), ( "2013-11-10T01:23:45-06:00", dt.datetime(2013, 11, 10, 1, 23, 45, tzinfo=central), ), ], ) def test_from_iso_datetime(value, expected): result = utils.from_iso_datetime(value) assert type(result) is dt.datetime assert result == expected def test_from_iso_time_with_microseconds(): t = dt.time(1, 23, 45, 6789) formatted = t.isoformat() result = utils.from_iso_time(formatted) assert type(result) is dt.time assert_time_equal(result, t) def test_from_iso_time_without_microseconds(): t = dt.time(1, 23, 45) formatted = t.isoformat() result = utils.from_iso_time(formatted) assert type(result) is dt.time assert_time_equal(result, t) def test_from_iso_date(): d = dt.date(2014, 8, 21) iso_date = d.isoformat() result = utils.from_iso_date(iso_date) assert type(result) is dt.date assert_date_equal(result, d) @pytest.mark.parametrize( ("value", "expected"), [ (1676386740, dt.datetime(2023, 2, 14, 14, 59, 00)), (1676386740.58, dt.datetime(2023, 2, 14, 14, 59, 00, 580000)), ], ) def test_from_timestamp(value, expected): result = utils.from_timestamp(value) assert type(result) is dt.datetime assert result == expected def test_from_timestamp_with_negative_value(): value = -10 with pytest.raises(ValueError, match=r"Not a valid POSIX timestamp"): utils.from_timestamp(value) def test_from_timestamp_with_overflow_value(): value = 9223372036854775 with pytest.raises(ValueError, match="out of range"): utils.from_timestamp(value) def test_get_func_args(): def f1(foo, bar): pass f2 = partial(f1, "baz") class F3: def __call__(self, foo, bar): pass f3 = F3() for func in [f1, f2, f3]: assert utils.get_func_args(func) == ["foo", "bar"] # Regression test for https://github.com/marshmallow-code/marshmallow/issues/540 def test_function_field_using_type_annotation(): def get_split_words(value: str): return value.split(";") class MySchema(Schema): friends = fields.Function(deserialize=get_split_words) data = {"friends": "Clark;Alfred;Robin"} result = MySchema().load(data) assert result == {"friends": ["Clark", "Alfred", "Robin"]} marshmallow-3.26.1/tests/test_validate.py000066400000000000000000001016571475016050200205070ustar00rootroot00000000000000"""Tests for marshmallow.validate""" import re import pytest from marshmallow import ValidationError, validate @pytest.mark.parametrize( "valid_url", [ "http://example.org", "https://example.org", "ftp://example.org", "ftps://example.org", "http://example.co.jp", "http://www.example.com/a%C2%B1b", "http://www.example.com/~username/", "http://info.example.com/?fred", "http://xn--mgbh0fb.xn--kgbechtv/", "http://example.com/blue/red%3Fand+green", "http://www.example.com/?array%5Bkey%5D=value", "http://xn--rsum-bpad.example.org/", "http://123.45.67.8/", "http://123.45.67.8:8329/", "http://[2001:db8::ff00:42]:8329", "http://[2001::1]:8329", "http://www.example.com:8000/foo", "http://user@example.com", "http://user:pass@example.com", "http://:pass@example.com", "http://@example.com", "http://AZaz09-._~%2A!$&'()*+,;=:@example.com", ], ) def test_url_absolute_valid(valid_url): validator = validate.URL(relative=False) assert validator(valid_url) == valid_url @pytest.mark.parametrize( "invalid_url", [ "http:///example.com/", "https:///example.com/", "https://example.org\\", "https://example.org\n", "ftp:///example.com/", "ftps:///example.com/", "http//example.org", "http:///", "http:/example.org", "foo://example.org", "../icons/logo.gif", "http://2001:db8::ff00:42:8329", "http://[192.168.1.1]:8329", "abc", "..", "/", " ", "", None, "http://user@pass@example.com", "http://@pass@example.com", "http://@@example.com", "http://^@example.com", "http://%0G@example.com", "http://%@example.com", ], ) def test_url_absolute_invalid(invalid_url): validator = validate.URL(relative=False) with pytest.raises(ValidationError): validator(invalid_url) @pytest.mark.parametrize( "valid_url", [ "http://example.org", "http://123.45.67.8/", "http://example.com/foo/bar/../baz", "https://example.com/../icons/logo.gif", "http://example.com/./icons/logo.gif", "ftp://example.com/../../../../g", "http://example.com/g?y/./x", "/foo/bar", "/foo?bar", "/foo?bar#baz", ], ) def test_url_relative_valid(valid_url): validator = validate.URL(relative=True) assert validator(valid_url) == valid_url @pytest.mark.parametrize( "invalid_url", [ "http//example.org", "http://example.org\n", "suppliers.html", "../icons/logo.gif", "icons/logo.gif", "../.../g", "...", "\\", " ", "", None, ], ) def test_url_relative_invalid(invalid_url): validator = validate.URL(relative=True) with pytest.raises(ValidationError): validator(invalid_url) @pytest.mark.parametrize( "valid_url", [ "/foo/bar", "/foo?bar", "?bar", "/foo?bar#baz", ], ) def test_url_relative_only_valid(valid_url): validator = validate.URL(relative=True, absolute=False) assert validator(valid_url) == valid_url @pytest.mark.parametrize( "invalid_url", [ "http//example.org", "http://example.org\n", "suppliers.html", "../icons/logo.gif", "icons/logo.gif", "../.../g", "...", "\\", " ", "", "http://example.org", "http://123.45.67.8/", "http://example.com/foo/bar/../baz", "https://example.com/../icons/logo.gif", "http://example.com/./icons/logo.gif", "ftp://example.com/../../../../g", "http://example.com/g?y/./x", ], ) def test_url_relative_only_invalid(invalid_url): validator = validate.URL(relative=True, absolute=False) with pytest.raises(ValidationError): validator(invalid_url) @pytest.mark.parametrize( "valid_url", [ "http://example.org", "http://123.45.67.8/", "http://example", "http://example.", "http://example:80", "http://user.name:pass.word@example", "http://example/foo/bar", ], ) def test_url_dont_require_tld_valid(valid_url): validator = validate.URL(require_tld=False) assert validator(valid_url) == valid_url @pytest.mark.parametrize( "invalid_url", [ "http//example", "http://example\n", "http://.example.org", "http:///foo/bar", "http:// /foo/bar", "", None, ], ) def test_url_dont_require_tld_invalid(invalid_url): validator = validate.URL(require_tld=False) with pytest.raises(ValidationError): validator(invalid_url) def test_url_custom_scheme(): validator = validate.URL() # By default, ws not allowed url = "ws://test.test" with pytest.raises(ValidationError): validator(url) validator = validate.URL(schemes={"http", "https", "ws"}) assert validator(url) == url @pytest.mark.parametrize( "valid_url", ( "file:///tmp/tmp1234", "file://localhost/tmp/tmp1234", "file:///C:/Users/test/file.txt", "file://localhost/C:/Program%20Files/file.exe", "file:///home/user/documents/test.pdf", "file:///tmp/test%20file.txt", "file:///", "file://localhost/", ), ) def test_url_accepts_valid_file_urls(valid_url): validator = validate.URL(schemes={"file"}) assert validator(valid_url) == valid_url @pytest.mark.parametrize( "invalid_url", ( "file://", "file:/tmp/file.txt", "file:tmp/file.txt", "file://hostname/path", "file:///tmp/test file.txt", ), ) def test_url_rejects_invalid_file_urls(invalid_url): validator = validate.URL(schemes={"file"}) with pytest.raises(ValidationError, match="Not a valid URL."): assert validator(invalid_url) def test_url_relative_and_custom_schemes(): validator = validate.URL(relative=True) # By default, ws not allowed url = "ws://test.test" with pytest.raises(ValidationError): validator(url) validator = validate.URL(relative=True, schemes={"http", "https", "ws"}) assert validator(url) == url def test_url_custom_message(): validator = validate.URL(error="{input} ain't an URL") with pytest.raises(ValidationError, match="invalid ain't an URL"): validator("invalid") def test_url_repr(): assert repr( validate.URL(relative=False, error=None) ) == "".format("Not a valid URL.") assert repr( validate.URL(relative=True, error="foo") ) == "".format("foo") assert repr( validate.URL(relative=True, absolute=False, error="foo") ) == "".format("foo") def test_url_rejects_invalid_relative_usage(): with pytest.raises( ValueError, match="URL validation cannot set both relative and absolute to False", ): validate.URL(relative=False, absolute=False) @pytest.mark.parametrize( "valid_email", [ "niceandsimple@example.com", "NiCeAnDsImPlE@eXaMpLe.CoM", "very.common@example.com", "a.little.lengthy.but.fine@a.iana-servers.net", "disposable.style.email.with+symbol@example.com", '"very.unusual.@.unusual.com"@example.com', "!#$%&'*+-/=?^_`{}|~@example.org", "niceandsimple@[64.233.160.0]", "niceandsimple@localhost", "josé@blah.com", "δοκ.ιμή@παράδειγμα.δοκιμή", ], ) def test_email_valid(valid_email): validator = validate.Email() assert validator(valid_email) == valid_email @pytest.mark.parametrize( "invalid_email", [ "niceandsimple\n@example.com", "NiCeAnDsImPlE@eXaMpLe.CoM\n", 'a"b(c)d,e:f;gi[j\\k]l@example.com', 'just"not"right@example.com', 'this is"not\allowed@example.com', 'this\\ still\\"not\\\\allowed@example.com', '"much.more unusual"@example.com', '"very.(),:;<>[]".VERY."very@\\ "very".unusual"@strange.example.com', '" "@example.org', "user@example", "@nouser.com", "example.com", "user", "", None, ], ) def test_email_invalid(invalid_email): validator = validate.Email() with pytest.raises(ValidationError): validator(invalid_email) def test_email_custom_message(): validator = validate.Email(error="{input} is not an email addy.") with pytest.raises(ValidationError, match="invalid is not an email addy."): validator("invalid") def test_email_repr(): assert repr(validate.Email(error=None)) == "".format( "Not a valid email address." ) assert repr(validate.Email(error="foo")) == "".format("foo") def test_range_min(): assert validate.Range(1, 2)(1) == 1 assert validate.Range(0)(1) == 1 assert validate.Range()(1) == 1 assert validate.Range(min_inclusive=False, max_inclusive=False)(1) == 1 assert validate.Range(1, 1)(1) == 1 with pytest.raises(ValidationError, match="Must be greater than or equal to 2"): validate.Range(2, 3)(1) with pytest.raises(ValidationError, match="Must be greater than or equal to 2"): validate.Range(2)(1) with pytest.raises(ValidationError, match="Must be greater than 1"): validate.Range(1, 2, min_inclusive=False, max_inclusive=True, error=None)(1) with pytest.raises(ValidationError, match="less than 1"): validate.Range(1, 1, min_inclusive=True, max_inclusive=False, error=None)(1) def test_range_max(): assert validate.Range(1, 2)(2) == 2 assert validate.Range(None, 2)(2) == 2 assert validate.Range()(2) == 2 assert validate.Range(min_inclusive=False, max_inclusive=False)(2) == 2 assert validate.Range(2, 2)(2) == 2 with pytest.raises(ValidationError, match="less than or equal to 1"): validate.Range(0, 1)(2) with pytest.raises(ValidationError, match="less than or equal to 1"): validate.Range(None, 1)(2) with pytest.raises(ValidationError, match="less than 2"): validate.Range(1, 2, min_inclusive=True, max_inclusive=False, error=None)(2) with pytest.raises(ValidationError, match="greater than 2"): validate.Range(2, 2, min_inclusive=False, max_inclusive=True, error=None)(2) def test_range_custom_message(): v = validate.Range(2, 3, error="{input} is not between {min} and {max}") with pytest.raises(ValidationError, match="1 is not between 2 and 3"): v(1) v = validate.Range(2, None, error="{input} is less than {min}") with pytest.raises(ValidationError, match="1 is less than 2"): v(1) v = validate.Range(None, 3, error="{input} is greater than {max}") with pytest.raises(ValidationError, match="4 is greater than 3"): v(4) def test_range_repr(): assert ( repr( validate.Range( min=None, max=None, error=None, min_inclusive=True, max_inclusive=True ) ) == "" ) assert ( repr( validate.Range( min=1, max=3, error="foo", min_inclusive=False, max_inclusive=False ) ) == "".format( "foo" ) ) def test_length_min(): assert validate.Length(3, 5)("foo") == "foo" assert validate.Length(3, 5)([1, 2, 3]) == [1, 2, 3] assert validate.Length(0)("a") == "a" assert validate.Length(0)([1]) == [1] assert validate.Length()("") == "" assert validate.Length()([]) == [] assert validate.Length(1, 1)("a") == "a" assert validate.Length(1, 1)([1]) == [1] with pytest.raises(ValidationError): validate.Length(4, 5)("foo") with pytest.raises(ValidationError): validate.Length(4, 5)([1, 2, 3]) with pytest.raises(ValidationError): validate.Length(5)("foo") with pytest.raises(ValidationError): validate.Length(5)([1, 2, 3]) def test_length_max(): assert validate.Length(1, 3)("foo") == "foo" assert validate.Length(1, 3)([1, 2, 3]) == [1, 2, 3] assert validate.Length(None, 1)("a") == "a" assert validate.Length(None, 1)([1]) == [1] assert validate.Length()("") == "" assert validate.Length()([]) == [] assert validate.Length(2, 2)("ab") == "ab" assert validate.Length(2, 2)([1, 2]) == [1, 2] with pytest.raises(ValidationError): validate.Length(1, 2)("foo") with pytest.raises(ValidationError): validate.Length(1, 2)([1, 2, 3]) with pytest.raises(ValidationError): validate.Length(None, 2)("foo") with pytest.raises(ValidationError): validate.Length(None, 2)([1, 2, 3]) def test_length_equal(): assert validate.Length(equal=3)("foo") == "foo" assert validate.Length(equal=3)([1, 2, 3]) == [1, 2, 3] assert validate.Length(equal=None)("") == "" assert validate.Length(equal=None)([]) == [] with pytest.raises(ValidationError): validate.Length(equal=2)("foo") with pytest.raises(ValidationError): validate.Length(equal=2)([1, 2, 3]) error_message = "The `equal` parameter was provided, maximum or minimum parameter must not be provided" with pytest.raises(ValueError, match=error_message): validate.Length(1, None, equal=3)("foo") with pytest.raises(ValueError, match=error_message): validate.Length(None, 5, equal=3)("foo") with pytest.raises(ValueError, match=error_message): validate.Length(1, 5, equal=3)("foo") def test_length_custom_message(): v = validate.Length(5, 6, error="{input} is not between {min} and {max}") with pytest.raises(ValidationError, match="foo is not between 5 and 6"): v("foo") v = validate.Length(5, None, error="{input} is shorter than {min}") with pytest.raises(ValidationError, match="foo is shorter than 5"): v("foo") v = validate.Length(None, 2, error="{input} is longer than {max}") with pytest.raises(ValidationError, match="foo is longer than 2"): v("foo") v = validate.Length(None, None, equal=4, error="{input} does not have {equal}") with pytest.raises(ValidationError, match="foo does not have 4"): v("foo") def test_length_repr(): assert ( repr(validate.Length(min=None, max=None, error=None, equal=None)) == "" ) assert repr( validate.Length(min=1, max=3, error="foo", equal=None) ) == "".format("foo") assert repr( validate.Length(min=None, max=None, error="foo", equal=5) ) == "".format("foo") def test_equal(): assert validate.Equal("a")("a") == "a" assert validate.Equal(1)(1) == 1 assert validate.Equal([1])([1]) == [1] with pytest.raises(ValidationError): validate.Equal("b")("a") with pytest.raises(ValidationError): validate.Equal(2)(1) with pytest.raises(ValidationError): validate.Equal([2])([1]) def test_equal_custom_message(): v = validate.Equal("a", error="{input} is not equal to {other}.") with pytest.raises(ValidationError, match="b is not equal to a."): v("b") def test_equal_repr(): assert repr( validate.Equal(comparable=123, error=None) ) == "".format("Must be equal to {other}.") assert repr( validate.Equal(comparable=123, error="foo") ) == "".format("foo") def test_regexp_str(): assert validate.Regexp(r"a")("a") == "a" assert validate.Regexp(r"\w")("_") == "_" assert validate.Regexp(r"\s")(" ") == " " assert validate.Regexp(r"1")("1") == "1" assert validate.Regexp(r"[0-9]+")("1") == "1" assert validate.Regexp(r"a", re.IGNORECASE)("A") == "A" with pytest.raises(ValidationError): validate.Regexp(r"[0-9]+")("a") with pytest.raises(ValidationError): validate.Regexp(r"[a-z]+")("1") with pytest.raises(ValidationError): validate.Regexp(r"a")("A") def test_regexp_compile(): assert validate.Regexp(re.compile(r"a"))("a") == "a" assert validate.Regexp(re.compile(r"\w"))("_") == "_" assert validate.Regexp(re.compile(r"\s"))(" ") == " " assert validate.Regexp(re.compile(r"1"))("1") == "1" assert validate.Regexp(re.compile(r"[0-9]+"))("1") == "1" assert validate.Regexp(re.compile(r"a", re.IGNORECASE))("A") == "A" assert validate.Regexp(re.compile(r"a", re.IGNORECASE), re.IGNORECASE)("A") == "A" with pytest.raises(ValidationError): validate.Regexp(re.compile(r"[0-9]+"))("a") with pytest.raises(ValidationError): validate.Regexp(re.compile(r"[a-z]+"))("1") with pytest.raises(ValidationError): validate.Regexp(re.compile(r"a"))("A") with pytest.raises(ValidationError): validate.Regexp(re.compile(r"a"), re.IGNORECASE)("A") def test_regexp_custom_message(): rex = r"[0-9]+" v = validate.Regexp(rex, error="{input} does not match {regex}") with pytest.raises(ValidationError, match="a does not match"): v("a") def test_regexp_repr(): assert repr( validate.Regexp(regex="abc", flags=0, error=None) ) == "".format( re.compile("abc"), "String does not match expected pattern." ) assert repr( validate.Regexp(regex="abc", flags=re.IGNORECASE, error="foo") ) == "".format( re.compile("abc", re.IGNORECASE), "foo" ) def test_predicate(): class Dummy: def _true(self): return True def _false(self): return False def _list(self): return [1, 2, 3] def _empty(self): return [] def _identity(self, arg): return arg d = Dummy() assert validate.Predicate("_true")(d) == d assert validate.Predicate("_list")(d) == d assert validate.Predicate("_identity", arg=True)(d) == d assert validate.Predicate("_identity", arg=1)(d) == d assert validate.Predicate("_identity", arg="abc")(d) == d with pytest.raises(ValidationError, match="Invalid input."): validate.Predicate("_false")(d) with pytest.raises(ValidationError): validate.Predicate("_empty")(d) with pytest.raises(ValidationError): validate.Predicate("_identity", arg=False)(d) with pytest.raises(ValidationError): validate.Predicate("_identity", arg=0)(d) with pytest.raises(ValidationError): validate.Predicate("_identity", arg="")(d) def test_predicate_custom_message(): class Dummy: def _false(self): return False def __str__(self): return "Dummy" d = Dummy() with pytest.raises(ValidationError, match="Dummy._false is invalid!"): validate.Predicate("_false", error="{input}.{method} is invalid!")(d) def test_predicate_repr(): assert repr( validate.Predicate(method="foo", error=None) ) == "".format( "foo", {}, "Invalid input." ) assert repr( validate.Predicate(method="foo", error="bar", zoo=1) ) == "".format( "foo", {"zoo": 1}, "bar" ) def test_noneof(): assert validate.NoneOf([1, 2, 3])(4) == 4 assert validate.NoneOf("abc")("d") == "d" assert validate.NoneOf("")([]) == [] assert validate.NoneOf([])("") == "" assert validate.NoneOf([])([]) == [] assert validate.NoneOf([1, 2, 3])(None) is None with pytest.raises(ValidationError, match="Invalid input."): validate.NoneOf([1, 2, 3])(3) with pytest.raises(ValidationError): validate.NoneOf("abc")("c") with pytest.raises(ValidationError): validate.NoneOf([1, 2, None])(None) with pytest.raises(ValidationError): validate.NoneOf("")("") def test_noneof_custom_message(): with pytest.raises(ValidationError, match=""): validate.NoneOf([1, 2], error="")(1) none_of = validate.NoneOf([1, 2], error="{input} cannot be one of {values}") with pytest.raises(ValidationError, match="1 cannot be one of 1, 2"): none_of(1) def test_noneof_repr(): assert repr( validate.NoneOf(iterable=[1, 2, 3], error=None) ) == "".format("Invalid input.") assert repr( validate.NoneOf(iterable=[1, 2, 3], error="foo") ) == "".format("foo") def test_oneof(): assert validate.OneOf([1, 2, 3])(2) == 2 assert validate.OneOf("abc")("b") == "b" assert validate.OneOf("")("") == "" assert validate.OneOf(dict(a=0, b=1))("a") == "a" assert validate.OneOf((1, 2, None))(None) is None with pytest.raises(ValidationError, match="Must be one of: 1, 2, 3."): validate.OneOf([1, 2, 3])(4) with pytest.raises(ValidationError): validate.OneOf("abc")("d") with pytest.raises(ValidationError): validate.OneOf((1, 2, 3))(None) with pytest.raises(ValidationError): validate.OneOf([])([]) with pytest.raises(ValidationError): validate.OneOf(())(()) with pytest.raises(ValidationError): validate.OneOf(dict(a=0, b=1))(0) with pytest.raises(ValidationError): validate.OneOf("123")(1) def test_oneof_options(): oneof = validate.OneOf([1, 2, 3], ["one", "two", "three"]) expected = [("1", "one"), ("2", "two"), ("3", "three")] assert list(oneof.options()) == expected oneof = validate.OneOf([1, 2, 3], ["one", "two"]) expected = [("1", "one"), ("2", "two"), ("3", "")] assert list(oneof.options()) == expected oneof = validate.OneOf([1, 2], ["one", "two", "three"]) expected = [("1", "one"), ("2", "two"), ("", "three")] assert list(oneof.options()) == expected oneof = validate.OneOf([1, 2]) expected = [("1", ""), ("2", "")] assert list(oneof.options()) == expected def test_oneof_text(): oneof = validate.OneOf([1, 2, 3], ["one", "two", "three"]) assert oneof.choices_text == "1, 2, 3" assert oneof.labels_text == "one, two, three" oneof = validate.OneOf([1], ["one"]) assert oneof.choices_text == "1" assert oneof.labels_text == "one" oneof = validate.OneOf(dict(a=0, b=1)) assert ", ".join(sorted(oneof.choices_text.split(", "))) == "a, b" assert oneof.labels_text == "" def test_oneof_custom_message(): oneof = validate.OneOf([1, 2, 3], error="{input} is not one of {choices}") expected = "4 is not one of 1, 2, 3" with pytest.raises(ValidationError): oneof(4) assert expected in str(expected) oneof = validate.OneOf( [1, 2, 3], ["one", "two", "three"], error="{input} is not one of {labels}" ) expected = "4 is not one of one, two, three" with pytest.raises(ValidationError): oneof(4) assert expected in str(expected) def test_oneof_repr(): assert repr( validate.OneOf(choices=[1, 2, 3], labels=None, error=None) ) == "".format( "Must be one of: {choices}." ) assert repr( validate.OneOf(choices=[1, 2, 3], labels=["a", "b", "c"], error="foo") ) == "".format( ["a", "b", "c"], "foo" ) def test_containsonly_in_list(): assert validate.ContainsOnly([])([]) == [] assert validate.ContainsOnly([1, 2, 3])([1]) == [1] assert validate.ContainsOnly([1, 1, 2])([1, 1]) == [1, 1] assert validate.ContainsOnly([1, 2, 3])([1, 2]) == [1, 2] assert validate.ContainsOnly([1, 2, 3])([2, 1]) == [2, 1] assert validate.ContainsOnly([1, 2, 3])([1, 2, 3]) == [1, 2, 3] assert validate.ContainsOnly([1, 2, 3])([3, 1, 2]) == [3, 1, 2] assert validate.ContainsOnly([1, 2, 3])([2, 3, 1]) == [2, 3, 1] assert validate.ContainsOnly([1, 2, 3])([1, 2, 3, 1]) == [1, 2, 3, 1] assert validate.ContainsOnly([1, 2, 3])([]) == [] with pytest.raises( ValidationError, match="One or more of the choices you made was not in: 1, 2, 3.", ): validate.ContainsOnly([1, 2, 3])([4]) with pytest.raises(ValidationError): validate.ContainsOnly([])([1]) def test_contains_only_unhashable_types(): assert validate.ContainsOnly([[1], [2], [3]])([[1]]) == [[1]] assert validate.ContainsOnly([[1], [1], [2]])([[1], [1]]) == [[1], [1]] assert validate.ContainsOnly([[1], [2], [3]])([[1], [2]]) == [[1], [2]] assert validate.ContainsOnly([[1], [2], [3]])([[2], [1]]) == [[2], [1]] assert validate.ContainsOnly([[1], [2], [3]])([[1], [2], [3]]) == [[1], [2], [3]] assert validate.ContainsOnly([[1], [2], [3]])([[3], [1], [2]]) == [[3], [1], [2]] assert validate.ContainsOnly([[1], [2], [3]])([[2], [3], [1]]) == [[2], [3], [1]] assert validate.ContainsOnly([[1], [2], [3]])([]) == [] with pytest.raises(ValidationError): validate.ContainsOnly([[1], [2], [3]])([[4]]) with pytest.raises(ValidationError): validate.ContainsOnly([])([1]) def test_containsonly_in_tuple(): assert validate.ContainsOnly(())(()) == () assert validate.ContainsOnly((1, 2, 3))((1,)) == (1,) assert validate.ContainsOnly((1, 1, 2))((1, 1)) == (1, 1) assert validate.ContainsOnly((1, 2, 3))((1, 2)) == (1, 2) assert validate.ContainsOnly((1, 2, 3))((2, 1)) == (2, 1) assert validate.ContainsOnly((1, 2, 3))((1, 2, 3)) == (1, 2, 3) assert validate.ContainsOnly((1, 2, 3))((3, 1, 2)) == (3, 1, 2) assert validate.ContainsOnly((1, 2, 3))((2, 3, 1)) == (2, 3, 1) assert validate.ContainsOnly((1, 2, 3))(()) == tuple() with pytest.raises(ValidationError): validate.ContainsOnly((1, 2, 3))((4,)) with pytest.raises(ValidationError): validate.ContainsOnly(())((1,)) def test_contains_only_in_string(): assert validate.ContainsOnly("")("") == "" assert validate.ContainsOnly("abc")("a") == "a" assert validate.ContainsOnly("aab")("aa") == "aa" assert validate.ContainsOnly("abc")("ab") == "ab" assert validate.ContainsOnly("abc")("ba") == "ba" assert validate.ContainsOnly("abc")("abc") == "abc" assert validate.ContainsOnly("abc")("cab") == "cab" assert validate.ContainsOnly("abc")("bca") == "bca" assert validate.ContainsOnly("abc")("") == "" with pytest.raises(ValidationError): validate.ContainsOnly("abc")("d") with pytest.raises(ValidationError): validate.ContainsOnly("")("a") def test_containsonly_custom_message(): containsonly = validate.ContainsOnly( [1, 2, 3], error="{input} is not one of {choices}" ) expected = "4, 5 is not one of 1, 2, 3" with pytest.raises(ValidationError): containsonly([4, 5]) assert expected in str(expected) containsonly = validate.ContainsOnly( [1, 2, 3], ["one", "two", "three"], error="{input} is not one of {labels}" ) expected = "4, 5 is not one of one, two, three" with pytest.raises(ValidationError): containsonly([4, 5]) assert expected in str(expected) def test_containsonly_repr(): assert repr( validate.ContainsOnly(choices=[1, 2, 3], labels=None, error=None) ) == "".format( "One or more of the choices you made was not in: {choices}." ) assert repr( validate.ContainsOnly(choices=[1, 2, 3], labels=["a", "b", "c"], error="foo") ) == "".format( ["a", "b", "c"], "foo" ) def test_containsnoneof_error_message(): with pytest.raises( ValidationError, match="One or more of the choices you made was in: 1" ): validate.ContainsNoneOf([1])([1]) with pytest.raises( ValidationError, match="One or more of the choices you made was in: 1, 2, 3" ): validate.ContainsNoneOf([1, 2, 3])([1]) with pytest.raises( ValidationError, match="One or more of the choices you made was in: one, two" ): validate.ContainsNoneOf(["one", "two"])(["one"]) with pytest.raises( ValidationError, match="One or more of the choices you made was in: @, !, &, ?" ): validate.ContainsNoneOf("@!&?")("@") def test_containsnoneof_in_list(): assert validate.ContainsNoneOf([])([]) == [] assert validate.ContainsNoneOf([])([1, 2, 3]) == [1, 2, 3] assert validate.ContainsNoneOf([4])([1, 2, 3]) == [1, 2, 3] assert validate.ContainsNoneOf([2])([1, 3, 4]) == [1, 3, 4] assert validate.ContainsNoneOf([1, 2, 3])([4]) == [4] assert validate.ContainsNoneOf([4])([1, 1, 1, 1]) == [1, 1, 1, 1] with pytest.raises(ValidationError): validate.ContainsNoneOf([1])([1, 2, 3]) with pytest.raises(ValidationError): validate.ContainsNoneOf([1, 1, 1])([1, 2, 3]) with pytest.raises(ValidationError): validate.ContainsNoneOf([1, 2])([1, 2]) with pytest.raises(ValidationError): validate.ContainsNoneOf([1])([1, 1, 1, 1]) def test_containsnoneof_unhashable_types(): assert validate.ContainsNoneOf([[1], [2], [3]])([]) == [] assert validate.ContainsNoneOf([[1], [2], [3]])([[4]]) == [[4]] assert validate.ContainsNoneOf([[1], [2], [3]])([[4], [4]]) == [[4], [4]] assert validate.ContainsNoneOf([[1], [2], [3]])([[4], [5]]) == [[4], [5]] with pytest.raises(ValidationError): validate.ContainsNoneOf([[1], [2], [3]])([[1]]) with pytest.raises(ValidationError): validate.ContainsNoneOf([[1], [2], [3]])([[1], [2]]) with pytest.raises(ValidationError): validate.ContainsNoneOf([[1], [2], [3]])([[2], [1]]) with pytest.raises(ValidationError): validate.ContainsNoneOf([[1], [2], [3]])([[1], [2], [3]]) with pytest.raises(ValidationError): validate.ContainsNoneOf([[1], [2], [3]])([[3], [2], [1]]) with pytest.raises(ValidationError): validate.ContainsNoneOf([[1], [2], [3]])([[2], [3], [1]]) def test_containsnoneof_in_tuple(): assert validate.ContainsNoneOf(())(()) == () assert validate.ContainsNoneOf(())((1, 2, 3)) == (1, 2, 3) assert validate.ContainsNoneOf((4,))((1, 2, 3)) == (1, 2, 3) assert validate.ContainsNoneOf((2,))((1, 3, 4)) == (1, 3, 4) assert validate.ContainsNoneOf((1, 2, 3))((4,)) == (4,) assert validate.ContainsNoneOf((4,))((1, 1, 1, 1)) == (1, 1, 1, 1) with pytest.raises(ValidationError): validate.ContainsNoneOf((1,))((1, 2, 3)) with pytest.raises(ValidationError): validate.ContainsNoneOf((1, 1, 1))((1, 2, 3)) with pytest.raises(ValidationError): validate.ContainsNoneOf((1, 2))((1, 2)) with pytest.raises(ValidationError): validate.ContainsNoneOf((1,))((1, 1, 1, 1)) def test_containsnoneof_in_string(): assert validate.ContainsNoneOf("")("") == "" assert validate.ContainsNoneOf("")("abc") == "abc" assert validate.ContainsNoneOf("d")("abc") == "abc" assert validate.ContainsNoneOf("b")("acd") == "acd" assert validate.ContainsNoneOf("abc")("d") == "d" assert validate.ContainsNoneOf("d")("aaaa") == "aaaa" with pytest.raises(ValidationError): validate.ContainsNoneOf("a")("abc") with pytest.raises(ValidationError): validate.ContainsNoneOf("aaa")("abc") with pytest.raises(ValidationError): validate.ContainsNoneOf("ab")("ab") with pytest.raises(ValidationError): validate.ContainsNoneOf("a")("aaaa") def test_containsnoneof_custom_message(): validator = validate.ContainsNoneOf( [1, 2, 3], error="{input} was in the banned list: {values}" ) expected = "1 was in the banned list: 1, 2, 3" with pytest.raises(ValidationError, match=expected): validator([1]) def test_containsnoneof_mixing_types(): with pytest.raises(ValidationError): validate.ContainsNoneOf("abc")(["a"]) with pytest.raises(ValidationError): validate.ContainsNoneOf(["a", "b", "c"])("a") with pytest.raises(ValidationError): validate.ContainsNoneOf((1, 2, 3))([1]) with pytest.raises(ValidationError): validate.ContainsNoneOf([1, 2, 3])((1,)) def is_even(value): if value % 2 != 0: raise ValidationError("Not an even value.") def test_and(): validator = validate.And(validate.Range(min=0), is_even) assert validator(2) with pytest.raises(ValidationError) as excinfo: validator(-1) errors = excinfo.value.messages assert errors == ["Must be greater than or equal to 0.", "Not an even value."] validator_with_composition = validate.And(validator, validate.Range(max=6)) assert validator_with_composition(4) with pytest.raises(ValidationError) as excinfo: validator_with_composition(7) errors = excinfo.value.messages assert errors == ["Not an even value.", "Must be less than or equal to 6."] marshmallow-3.26.1/tests/test_version_attributes.py000066400000000000000000000005201475016050200226340ustar00rootroot00000000000000# ruff: noqa: B018 import pytest import marshmallow def test_version_attributes_deprecated(): with pytest.warns(DeprecationWarning): marshmallow.__version__ with pytest.warns(DeprecationWarning): marshmallow.__parsed_version__ with pytest.warns(DeprecationWarning): marshmallow.__version_info__ marshmallow-3.26.1/tox.ini000066400000000000000000000015341475016050200154470ustar00rootroot00000000000000[tox] envlist = lint,mypy,py{39,310,311,312,313},docs [testenv] extras = tests commands = pytest {posargs} [testenv:lint] deps = pre-commit>=3.5,<5.0 skip_install = true commands = pre-commit run --all-files [testenv:mypy] deps = mypy>=1.14.1 types-simplejson commands = mypy --show-error-codes [testenv:docs] extras = docs commands = sphinx-build --fresh-env docs/ docs/_build {posargs} ; Below tasks are for development only (not run in CI) [testenv:docs-serve] deps = sphinx-autobuild extras = docs commands = sphinx-autobuild --port=0 --open-browser --delay=2 docs/ docs/_build {posargs} --watch src --watch CONTRIBUTING.rst --watch README.rst [testenv:readme-serve] deps = restview skip_install = true commands = restview README.rst [testenv:benchmark] usedevelop = true commands = python performance/benchmark.py --iterations=100 --repeat=3