pax_global_header00006660000000000000000000000064131463361160014516gustar00rootroot0000000000000052 comment=637f345ab3b76d10746877276fe98be3a6309f0c marshmallow-3.0.0b3/000077500000000000000000000000001314633611600142715ustar00rootroot00000000000000marshmallow-3.0.0b3/.gitignore000066400000000000000000000010271314633611600162610ustar00rootroot00000000000000*.py[cod] # C extensions *.so # Packages *.egg *.egg-info dist build eggs parts bin var sdist develop-eggs .installed.cfg lib lib64 # Installer logs pip-log.txt # Unit test / coverage reports .coverage htmlcov .tox nosetests.xml .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 # Other .directory *.pprof marshmallow-3.0.0b3/.travis.yml000066400000000000000000000012331314633611600164010ustar00rootroot00000000000000# Config file for automatic testing at travis-ci.org language: python # http://blog.travis-ci.com/2014-12-17-faster-builds-with-container-based-infrastructure/ sudo: false python: - "3.6" - "3.5" - "3.4" - "2.7" - "pypy" before_install: - pip install -U pip install: - pip install -U .[reco] - pip install -U -r dev-requirements.txt script: invoke test deploy: provider: pypi user: sloria on: tags: true distributions: sdist bdist_wheel password: secure: ESoVNuEqjuNGCCGMbjHwIJS3LfPL2BKPQG5RuDiW8qo4+BN25yobiD+Bo7kozUtUHVKa6oTdi4Gqb1i5IUKNcaP0qBItdkk3LPOAUyOhdFZF+dL22JhajJ6LNO7i1cyUTzjYaS2cOTG8kSSYd7W9Okfp5bY6HtUNgCqhI1mZfMU= marshmallow-3.0.0b3/AUTHORS.rst000066400000000000000000000112041314633611600161460ustar00rootroot00000000000000******* Authors ******* Lead ==== - Steven Loria `@sloria `_ 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 `_ marshmallow-3.0.0b3/CHANGELOG.rst000066400000000000000000001234571314633611600163260ustar00rootroot00000000000000Changelog --------- 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 release 2.13.5. - *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.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 overriden 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`` paramater to ``fields.Url`` that allows for relative URLs. 0.1.0 (2013-11-10) ++++++++++++++++++ * First release. marshmallow-3.0.0b3/CONTRIBUTING.rst000066400000000000000000000117611314633611600167400ustar00rootroot00000000000000Contributing Guidelines ======================= 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 simple 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 "please help" `_). The next section details how to contribute code. Contributing Code ----------------- In General ++++++++++ - `PEP 8`_, when sensible. - Test ruthlessly. Write docs for new features. - Even more important than Test-Driven Development--*Human-Driven Development*. .. _`PEP 8`: http://www.python.org/dev/peps/pep-0008/ In Particular +++++++++++++ Setting Up for Local Development ******************************** 1. Fork marshmallow_ on Github. :: $ git clone https://github.com/marshmallow-code/marshmallow.git $ cd marshmallow 2. Install development requirements. It is highly recommended that you use a virtualenv. :: # After activating your virtualenv $ pip install -r dev-requirements.txt 3. Install marshmallow in develop mode. :: $ pip install -e . Git Branch Structure ******************** Marshmallow abides by the following branching model: ``dev`` Current development branch. **New features should branch off here**. ``pypi`` Current production release on PyPI. ``X.Y-line`` Maintenance branch for release ``X.Y``. **Bug fixes should be sent to the most recent release branch.** The 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 $ git checkout -b name-of-feature dev # For a bugfix $ git checkout -b fix-something 2.x-line 2. Commit your changes. Write `good commit messages `_. :: $ 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 `Travis CI `_ build must be passing before your pull request is merged. Running tests ************* To run all tests: :: $ invoke test To run tests on Python 2.7, 3.4, 3.5, and PyPy virtual environments (must have each interpreter installed): :: $ tox .. _contributing_documentation: Documentation ************* Contributions to the documentation are welcome. Documentation is written in `reStructured Text`_ (rST). A quick rST reference can be found `here `_. Builds are powered by Sphinx_. To install the packages for building the docs, run the following in the root of the project: :: $ pip install -r docs/requirements.txt To build the docs: :: $ invoke docs -b The ``-b`` (for "browse") automatically opens up the docs in your browser after building. You can also build the docs in "watch" mode: :: $ pip install sphinx-autobuild $ invoke docs -wb Changes in the `docs/` directory 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: http://sphinx.pocoo.org/ .. _`reStructured Text`: http://docutils.sourceforge.net/rst.html .. _marshmallow: https://github.com/marshmallow-code/marshmallow marshmallow-3.0.0b3/LICENSE000066400000000000000000000020341314633611600152750ustar00rootroot00000000000000Copyright 2017 Steven Loria 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.0.0b3/MANIFEST.in000066400000000000000000000004671314633611600160360ustar00rootroot00000000000000include *.rst LICENSE NOTICE recursive-include tests * recursive-include examples * recursive-include docs * recursive-exclude docs *.pyc recursive-exclude docs *.pyo recursive-exclude tests *.pyc recursive-exclude tests *.pyo recursive-exclude examples *.pyc recursive-exclude examples *.pyo prune docs/_build marshmallow-3.0.0b3/NOTICE000066400000000000000000000124731314633611600152040ustar00rootroot00000000000000marshmallow includes code adapted from other Python libraries, including Flask-RESTful, Django, pytz, and six. Flask-RESTful License ===================== Copyright (c) 2013, Twilio, Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - 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. - Neither the name of the Twilio, Inc. 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 HOLDER 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. 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. pytz License ============ Copyright (c) 2003-2005 Stuart Bishop 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. six License =========== Copyright (c) 2010-2015 Benjamin Peterson 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.0.0b3/README.rst000066400000000000000000000051211314633611600157570ustar00rootroot00000000000000******************************************** marshmallow: simplified object serialization ******************************************** .. image:: https://badge.fury.io/py/marshmallow.svg :target: http://badge.fury.io/py/marshmallow :alt: Latest version .. image:: https://travis-ci.org/marshmallow-code/marshmallow.svg?branch=pypi :target: https://travis-ci.org/marshmallow-code/marshmallow :alt: Travis-CI .. image:: https://readthedocs.org/projects/marshmallow/badge/ :target: http://marshmallow.readthedocs.io/ :alt: Documentation **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 marshmallow import Schema, fields, pprint 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.data, 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 ========== :: $ pip install -U marshmallow --pre Documentation ============= Full documentation is available at http://marshmallow.readthedocs.io/ . Requirements ============ - Python >= 2.7 or >= 3.4 marshmallow has no external dependencies outside of the Python standard library, although `python-dateutil `_ is recommended for robust datetime deserialization. Ecosystem ========= A list of marshmallow-related libraries can be found at the GitHub wiki here: https://github.com/marshmallow-code/marshmallow/wiki/Ecosystem Project Links ============= - Docs: http://marshmallow.readthedocs.io/ - Changelog: http://marshmallow.readthedocs.io/en/latest/changelog.html - PyPI: https://pypi.python.org/pypi/marshmallow - Issues: https://github.com/marshmallow-code/marshmallow/issues License ======= MIT licensed. See the bundled `LICENSE `_ file for more details. marshmallow-3.0.0b3/dev-requirements.txt000066400000000000000000000002661314633611600203350ustar00rootroot00000000000000# Task execution invoke>=0.13.0 # Soft dependencies python-dateutil pytz # Distribution wheel twine # Testing pytest==3.0.2 tox>=1.5.0 simplejson # Syntax checking flake8==2.4.1 marshmallow-3.0.0b3/docs/000077500000000000000000000000001314633611600152215ustar00rootroot00000000000000marshmallow-3.0.0b3/docs/.gitignore000066400000000000000000000000431314633611600172060ustar00rootroot00000000000000marshmallow.docset marshmallow.tgz marshmallow-3.0.0b3/docs/Makefile000066400000000000000000000154151314633611600166670ustar00rootroot00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build DASHING = dashing # User-friendly check for sphinx-build ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) endif # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext dash help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" @echo " text to make text files" @echo " man to make manual pages" @echo " texinfo to make Texinfo files" @echo " info to make Texinfo files and run them through makeinfo" @echo " gettext to make PO message catalogs" @echo " changes to make an overview of all changed/added/deprecated items" @echo " xml to make Docutils-native XML files" @echo " pseudoxml to make pseudoxml-XML files for display purposes" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: rm -rf $(BUILDDIR)/* rm -rf marshmallow.docset marshmallow.tgz html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/complexity.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/complexity.qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/complexity" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/complexity" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." $(MAKE) -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." latexpdfja: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through platex and dvipdfmx..." $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." texinfo: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." @echo "Run \`make' in that directory to run these through makeinfo" \ "(use \`make info' here to do that automatically)." info: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo "Running Texinfo files through makeinfo..." make -C $(BUILDDIR)/texinfo info @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale @echo @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." xml: $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml @echo @echo "Build finished. The XML files are in $(BUILDDIR)/xml." pseudoxml: $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml @echo @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." dash: html $(DASHING) build @tar czf marshmallow.tgz marshmallow.docset marshmallow-3.0.0b3/docs/_static/000077500000000000000000000000001314633611600166475ustar00rootroot00000000000000marshmallow-3.0.0b3/docs/_static/marshmallow-logo.png000066400000000000000000000331241314633611600226440ustar00rootroot00000000000000PNG  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|,Useful Links
    {% for text, uri in theme_extra_nav_links.items() %}
  • {{text}}
  • {% endfor %}
marshmallow-3.0.0b3/docs/about.rst.inc000066400000000000000000000024021314633611600176330ustar00rootroot00000000000000**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 marshmallow import Schema, fields, pprint 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.data, 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:: bash $ pip install -U marshmallow --pre Ready to get started? Go on to the :ref:`Quickstart tutorial ` or check out some :ref:`Examples `. marshmallow-3.0.0b3/docs/api_reference.rst000066400000000000000000000016341314633611600205460ustar00rootroot00000000000000.. _api: ************* API Reference ************* .. module:: marshmallow Schema ====== .. autoclass:: marshmallow.Schema :inherited-members: .. autoclass:: marshmallow.SchemaOpts .. autoclass:: marshmallow.MarshalResult .. autoclass:: marshmallow.UnmarshalResult .. autofunction:: marshmallow.pprint .. _api_fields: Fields ====== .. automodule:: marshmallow.fields :members: :private-members: Decorators ========== .. automodule:: marshmallow.decorators :members: Validators ========== .. automodule:: marshmallow.validate :members: Utility Functions ================= .. automodule:: marshmallow.utils :members: Marshalling =========== .. automodule:: marshmallow.marshalling :members: :private-members: Class Registry ============== .. automodule:: marshmallow.class_registry :members: Exceptions ========== .. automodule:: marshmallow.exceptions :members: marshmallow-3.0.0b3/docs/authors.rst000066400000000000000000000000341314633611600174350ustar00rootroot00000000000000.. include:: ../AUTHORS.rst marshmallow-3.0.0b3/docs/changelog.rst000066400000000000000000000000561314633611600177030ustar00rootroot00000000000000.. _changelog: .. include:: ../CHANGELOG.rst marshmallow-3.0.0b3/docs/conf.py000066400000000000000000000057751314633611600165360ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # marshmallow documentation build configuration file. # # This file is execfile()d with the current directory set to its containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. from collections import OrderedDict import sys import os import datetime as dt import alabaster # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. sys.path.insert(0, os.path.abspath('..')) import marshmallow # flake8: noqa # -- General configuration ----------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. #needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.intersphinx', 'sphinx.ext.viewcode', 'alabaster', 'sphinx_issues', ] primary_domain = 'py' default_role = 'py:obj' intersphinx_mapping = { 'python': ('http://python.readthedocs.io/en/latest/', None), } issues_github_path = 'marshmallow-code/marshmallow' # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The master toctree document. master_doc = 'index' # General information about the project. project = u'marshmallow' copyright = ' {0:%Y} Steven Loria'.format( dt.datetime.utcnow() ) version = release = marshmallow.__version__ exclude_patterns = ['_build'] # THEME html_theme_path = [alabaster.get_path()] html_theme = 'alabaster' html_static_path = ['_static'] templates_path = ['_templates'] html_show_sourcelink = False html_theme_options = { 'logo': 'marshmallow-logo.png', 'description': 'Object serialization and deserialization, lightweight and fluffy.', 'description_font_style': 'italic', 'github_user': 'marshmallow-code', 'github_repo': 'marshmallow', 'github_banner': True, 'github_type': 'star', 'donate_url': 'https://www.paypal.me/StevenLoria', 'code_font_size': '0.8em', 'warn_bg': '#FFC', 'warn_border': '#EEE', # Used to populate the useful-links.html template 'extra_nav_links': OrderedDict([ ('marshmallow @ PyPI', 'http://pypi.python.org/pypi/marshmallow'), ('marshmallow @ GitHub', 'http://github.com/marshmallow-code/marshmallow'), ('Issue Tracker', 'http://github.com/marshmallow-code/marshmallow/issues'), ]) } html_sidebars = { 'index': [ 'about.html', 'useful-links.html', 'searchbox.html', ], '**': ['about.html', 'useful-links.html', 'localtoc.html', 'relations.html', 'searchbox.html'] } marshmallow-3.0.0b3/docs/contributing.rst000066400000000000000000000000411314633611600204550ustar00rootroot00000000000000.. include:: ../CONTRIBUTING.rst marshmallow-3.0.0b3/docs/custom_fields.rst000066400000000000000000000127411314633611600206200ustar00rootroot00000000000000 .. _custom_fields: Custom 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 class Titlecased(fields.Field): def _serialize(self, value, attr, obj): if value is None: return '' return value.title() class UserSchema(Schema): name = fields.String() email = fields.String() created_at = fields.DateTime() titlename = TitleCased(attribute="name") 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.data['balance'] # => 100.0 .. _adding-context: Adding Context to `Method` and `Function` Fields ------------------------------------------------ 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 the a certain word appears in a ``Blog's`` title. .. code-block:: python :emphasize-lines: 4,8,16 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') # Method fields also optionally receive context argument 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} data, errors = schema.dump(user) data['is_author'] # => True data['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 :ref:`Extending Schemas ` page. - For example applications using marshmallow, check out the :ref:`Examples ` page. marshmallow-3.0.0b3/docs/dashing.json000066400000000000000000000007251314633611600175350ustar00rootroot00000000000000{ "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.0.0b3/docs/ecosystem.rst000066400000000000000000000002431314633611600177650ustar00rootroot00000000000000Ecosystem ========= A list of marshmallow-related libraries can be found at the GitHub wiki here: https://github.com/marshmallow-code/marshmallow/wiki/Ecosystem marshmallow-3.0.0b3/docs/examples.rst000066400000000000000000000131141314633611600175710ustar00rootroot00000000000000.. _examples: .. module:: marshmallow ******** Examples ******** The examples below will use `httpie `_ (a curl-like tool) for testing the APIs. Text Analysis API (Bottle + TextBlob) ===================================== Here is a very simple text analysis API using `Bottle `_ and `TextBlob `_ that demonstrates how to declare an object serializer. Assume that ``TextBlob`` objects have ``polarity``, ``subjectivity``, ``noun_phrase``, ``tags``, and ``words`` properties. .. literalinclude:: ../examples/textblob_example.py :language: python **Using The API** First, run the app. .. code-block:: bash $ python textblob_example.py Then send a POST request with some text. .. code-block:: bash $ http POST :5000/api/v1/analyze text="Simple is better" HTTP/1.0 200 OK Content-Length: 189 Content-Type: application/json Date: Wed, 13 Nov 2013 08:58:40 GMT Server: WSGIServer/0.1 Python/2.7.5 { "chunks": [ "simple" ], "discrete_sentiment": "positive", "polarity": 0.25, "subjectivity": 0.4285714285714286, "tags": [ [ "Simple", "NN" ], [ "is", "VBZ" ], [ "better", "JJR" ] ], "word_count": 3 } 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: - Validation and deserialization using :meth:`Schema.load`. - 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:: bash $ python flask_example.py First we'll POST some quotes. .. code-block:: bash $ 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:: bash $ 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:: bash $ 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:: bash $ 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 } ] } ToDo API (Flask + Peewee) ========================= This example uses Flask and the `Peewee `_ ORM to create a basic Todo application. Here, we use `Schema.load ` to validate and deserialize input data to model data. Also notice how `pre_load ` is used to clean input data and `post_load ` is used to add an envelope to response data. .. literalinclude:: ../examples/peewee_example.py :language: python **Using the API** After registering a user and creating some todo items in the database, here is an example response. .. code-block:: bash $ http GET :5000/todos/ { "todos": [ { "content": "Install marshmallow", "done": false, "id": 1, "posted_on": "2015-05-05T01:51:12.832232+00:00", "user": { "user": { "email": "foo@bar.com", "id": 1 } } }, { "content": "Learn Python", "done": false, "id": 2, "posted_on": "2015-05-05T01:51:20.728052+00:00", "user": { "user": { "email": "foo@bar.com", "id": 1 } } }, { "content": "Refactor everything", "done": false, "id": 3, "posted_on": "2015-05-05T01:51:25.970153+00:00", "user": { "user": { "email": "foo@bar.com", "id": 1 } } } ] } marshmallow-3.0.0b3/docs/extending.rst000066400000000000000000000332161314633611600177450ustar00rootroot00000000000000.. _extending: .. module:: marshmallow Extending Schemas ================= Pre-processing and Post-processing Methods ------------------------------------------ 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 :emphasize-lines: 7 from marshmallow import Schema, fields, pre_load 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 schema = UserSchema() result, errors = 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 at runtime. In cases where your pre- and post-processing methods need to receive the input collection when ``many=True``, add ``pass_many=True`` to the method decorators. The method will receive the input data (which may be a single datum or a collection) and the boolean value of ``many``. Example: Enveloping +++++++++++++++++++ One common use case is to wrap data in a namespace upon serialization and unwrap the data during deserialization. .. code-block:: python :emphasize-lines: 17,18,22,23,27,28 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): key = self.get_envelope_key(many) return data[key] @post_dump(pass_many=True) def wrap_with_envelope(self, data, many): key = self.get_envelope_key(many) return {key: data} @post_load def make_object(self, data): 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).data # {'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).data # {'users': [{'email': 'keith@stones.org', 'name': 'Keith'}, # {'email': 'charlie@stones.org', 'name': 'Charlie'}]} user_objs = user_schema.load(users_data, many=True).data # [, ] 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): if 'data' not in data: raise ValidationError('Input data must have a "data" key.') return data['data'] sch = BandSchema() sch.load({'name': 'The Band'}).errors # {'_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 :emphasize-lines: 9 from marshmallow import Schema, fields, ValidationError, pre_load class BandSchema(Schema): name = fields.Str() @pre_load def unwrap_envelope(self, data): if 'data' not in data: raise ValidationError('Input data must have a "data" key.', '_preprocessing') return data['data'] sch = BandSchema() sch.load({'name': 'The Band'}).errors # {'_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. ``@post_load(pass_many=True)`` methods 5. ``@post_load(pass_many=False)`` methods The pipeline for serialization is similar, except that the "pass_many" processors are invoked *after* the "non-raw" processors. 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.Field() @pre_load def preprocess(self, data): 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.Field() @pre_load def step1(self, data): # ... # Depends on step1 @pre_load def step2(self, data): # ... Handling Errors --------------- By default, :meth:`Schema.dump` and :meth:`Schema.load` will return validation errors as a dictionary (unless ``strict`` mode is enabled). You can specify a custom error-handling function for a :class:`Schema` by overriding the `handle_error ` method. The method receives the `ValidationError ` and the original object (or input data if deserializing) to be (de)serialized. .. code-block:: python :emphasize-lines: 10-13 import logging from marshmallow import Schema, fields class AppError(Exception): pass class UserSchema(Schema): email = fields.Email() def handle_error(self, exc, data): """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 .. _schemavalidation: Schema-level Validation ----------------------- You can register schema-level validation functions for a :class:`Schema` using the :meth:`marshmallow.validates_schema ` decorator. Schema-level validation errors will be stored on the ``_schema`` key of the errors dictonary. .. code-block:: python :emphasize-lines: 7 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): if data['field_b'] >= data['field_a']: raise ValidationError('field_a must be greater than field_b') schema = NumberSchema() result, errors = schema.load({'field_a': 2, 'field_b': 1}) errors['_schema'] # => ["field_a must be greater than field_b"] Validating Original Input Data ++++++++++++++++++++++++++++++ Normally, unspecified field names are ignored by the validator. If you would like access to the original, raw input (e.g. to fail validation if an unknown field name is sent), add ``pass_original=True`` to your call to `validates_schema `. .. code-block:: python :emphasize-lines: 7 from marshmallow import Schema, fields, validates_schema, ValidationError class MySchema(Schema): foo = fields.Int() bar = fields.Int() @validates_schema(pass_original=True) def check_unknown_fields(self, data, original_data): unknown = set(original_data) - set(self.fields) if unknown: raise ValidationError('Unknown field', unknown) schema = MySchema() errors = schema.load({'foo': 1, 'bar': 2, 'baz': 3, 'bu': 4}).errors # {'baz': 'Unknown field', 'bu': 'Unknown field'} Storing Errors on Specific Fields +++++++++++++++++++++++++++++++++ If you want to store schema-level validation errors on a specific field, you can pass a field name (or multiple field names) to the :exc:`ValidationError `. .. code-block:: python :emphasize-lines: 10 class NumberSchema(Schema): field_a = fields.Integer() field_b = fields.Integer() @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' ) schema = NumberSchema() result, errors = schema.load({'field_a': 2, 'field_b': 1}) errors['field_a'] # => ["field_a must be greater than field_b"] Overriding how attributes are accessed -------------------------------------- By default, marshmallow uses the `utils.get_value` function 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 :emphasize-lines: 7-8 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) Custom "class Meta" Options --------------------------- ``class Meta`` options are a way to configure and modify a :class:`Schema's ` behavior. See the :class:`API docs ` for a listing of available options. You can add custom ``class Meta`` options by subclassing :class:`SchemaOpts`. Example: Enveloping, Revisited ++++++++++++++++++++++++++++++ Let's build upon the example 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 :emphasize-lines: 3 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 :emphasize-lines: 1,2 class NamespacedSchema(Schema): OPTIONS_CLASS = NamespaceOpts @pre_load(pass_many=True) def unwrap_envelope(self, data, many): 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): 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 :emphasize-lines: 1,6,7 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.data # {"user": {"name": "Keith", "email": "keith@stones.com"}} 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.0.0b3/docs/index.rst000066400000000000000000000016621314633611600170670ustar00rootroot00000000000000.. marshmallow documentation master file ******************************************** marshmallow: simplified object serialization ******************************************** Release v\ |version|. (:ref:`Changelog `) .. include:: about.rst.inc Upgrading from an older version? ================================ See the :ref:`Upgrading to Newer Releases ` page for notes on getting your code up-to-date with the latest version. Why another library? ===================== See :ref:`this document ` to learn about what makes marshmallow unique. Guide ===== .. toctree:: :maxdepth: 2 install quickstart nesting custom_fields extending examples API Reference ============= .. toctree:: :maxdepth: 2 api_reference Project Info ============ .. toctree:: :maxdepth: 1 why changelog upgrading ecosystem license authors contributing kudos marshmallow-3.0.0b3/docs/install.rst000066400000000000000000000021001314633611600174120ustar00rootroot00000000000000.. _install: Installation ============ **marshmallow** requires Python >= 2.7 or >= 3.4. It has no external dependencies other than the Python standard library. .. note:: The `python-dateutil `_ package is not a hard dependency, but it is recommended for robust datetime deserialization. :: $ pip install python-dateutil Installing/Upgrading from the PyPI ---------------------------------- To install the latest stable version from the PyPI: :: $ pip install -U marshmallow To install the latest pre-release version from the PyPI: :: $ pip install -U marshmallow --pre To install marshmallow with the recommended soft dependencies: :: $ pip install -U marshmallow[reco] Get the Bleeding Edge Version ----------------------------- To get the latest development version of marshmallow, run :: $ pip install -U git+https://github.com/marshmallow-code/marshmallow.git@dev .. seealso:: Need help upgrading to newer releases? See the :ref:`Upgrading to Newer Releases ` page. marshmallow-3.0.0b3/docs/kudos.rst000066400000000000000000000005031314633611600170760ustar00rootroot00000000000000***** Kudos ***** A hat tip to `Django Rest Framework`_ , `Flask-RESTful`_, and `colander`_ for ideas and API design. .. _Flask-RESTful: http://flask-restful.readthedocs.io/en/latest/ .. _Django Rest Framework: http://django-rest-framework.org/ .. _colander: http://docs.pylonsproject.org/projects/colander/en/latest/ marshmallow-3.0.0b3/docs/license.rst000066400000000000000000000000601314633611600173710ustar00rootroot00000000000000License ======= .. literalinclude:: ../LICENSE marshmallow-3.0.0b3/docs/make.bat000066400000000000000000000145021314633611600166300ustar00rootroot00000000000000@ECHO OFF REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set BUILDDIR=_build set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . set I18NSPHINXOPTS=%SPHINXOPTS% . if NOT "%PAPER%" == "" ( set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% ) if "%1" == "" goto help if "%1" == "help" ( :help echo.Please use `make ^` where ^ is one of echo. html to make standalone HTML files echo. dirhtml to make HTML files named index.html in directories echo. singlehtml to make a single large HTML file echo. pickle to make pickle files echo. json to make JSON files echo. htmlhelp to make HTML files and a HTML help project echo. qthelp to make HTML files and a qthelp project echo. devhelp to make HTML files and a Devhelp project echo. epub to make an epub echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter echo. text to make text files echo. man to make manual pages echo. texinfo to make Texinfo files echo. gettext to make PO message catalogs echo. changes to make an overview over all changed/added/deprecated items echo. xml to make Docutils-native XML files echo. pseudoxml to make pseudoxml-XML files for display purposes echo. linkcheck to check all external links for integrity echo. doctest to run all doctests embedded in the documentation if enabled goto end ) if "%1" == "clean" ( for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i del /q /s %BUILDDIR%\* goto end ) %SPHINXBUILD% 2> nul if errorlevel 9009 ( echo. echo.The 'sphinx-build' command was not found. Make sure you have Sphinx echo.installed, then set the SPHINXBUILD environment variable to point echo.to the full path of the 'sphinx-build' executable. Alternatively you echo.may add the Sphinx directory to PATH. echo. echo.If you don't have Sphinx installed, grab it from echo.http://sphinx-doc.org/ exit /b 1 ) if "%1" == "html" ( %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/html. goto end ) if "%1" == "dirhtml" ( %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. goto end ) if "%1" == "singlehtml" ( %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. goto end ) if "%1" == "pickle" ( %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the pickle files. goto end ) if "%1" == "json" ( %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the JSON files. goto end ) if "%1" == "htmlhelp" ( %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run HTML Help Workshop with the ^ .hhp project file in %BUILDDIR%/htmlhelp. goto end ) if "%1" == "qthelp" ( %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run "qcollectiongenerator" with the ^ .qhcp project file in %BUILDDIR%/qthelp, like this: echo.^> qcollectiongenerator %BUILDDIR%\qthelp\complexity.qhcp echo.To view the help file: echo.^> assistant -collectionFile %BUILDDIR%\qthelp\complexity.ghc goto end ) if "%1" == "devhelp" ( %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp if errorlevel 1 exit /b 1 echo. echo.Build finished. goto end ) if "%1" == "epub" ( %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub if errorlevel 1 exit /b 1 echo. echo.Build finished. The epub file is in %BUILDDIR%/epub. goto end ) if "%1" == "latex" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex if errorlevel 1 exit /b 1 echo. echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. goto end ) if "%1" == "latexpdf" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex cd %BUILDDIR%/latex make all-pdf cd %BUILDDIR%/.. echo. echo.Build finished; the PDF files are in %BUILDDIR%/latex. goto end ) if "%1" == "latexpdfja" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex cd %BUILDDIR%/latex make all-pdf-ja cd %BUILDDIR%/.. echo. echo.Build finished; the PDF files are in %BUILDDIR%/latex. goto end ) if "%1" == "text" ( %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text if errorlevel 1 exit /b 1 echo. echo.Build finished. The text files are in %BUILDDIR%/text. goto end ) if "%1" == "man" ( %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man if errorlevel 1 exit /b 1 echo. echo.Build finished. The manual pages are in %BUILDDIR%/man. goto end ) if "%1" == "texinfo" ( %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo if errorlevel 1 exit /b 1 echo. echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. goto end ) if "%1" == "gettext" ( %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale if errorlevel 1 exit /b 1 echo. echo.Build finished. The message catalogs are in %BUILDDIR%/locale. goto end ) if "%1" == "changes" ( %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes if errorlevel 1 exit /b 1 echo. echo.The overview file is in %BUILDDIR%/changes. goto end ) if "%1" == "linkcheck" ( %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck if errorlevel 1 exit /b 1 echo. echo.Link check complete; look for any errors in the above output ^ or in %BUILDDIR%/linkcheck/output.txt. goto end ) if "%1" == "doctest" ( %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest if errorlevel 1 exit /b 1 echo. echo.Testing of doctests in the sources finished, look at the ^ results in %BUILDDIR%/doctest/output.txt. goto end ) if "%1" == "xml" ( %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml if errorlevel 1 exit /b 1 echo. echo.Build finished. The XML files are in %BUILDDIR%/xml. goto end ) if "%1" == "pseudoxml" ( %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml if errorlevel 1 exit /b 1 echo. echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. goto end ) :endmarshmallow-3.0.0b3/docs/nesting.rst000066400000000000000000000167171314633611600174360ustar00rootroot00000000000000 .. _nesting: Nesting 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 object. .. code-block:: python :emphasize-lines: 14 import datetime as dt class User(object): def __init__(self, name, email): self.name = name self.email = email self.created_at = dt.datetime.now() self.friends = [] self.employer = None class Blog(object): def __init__(self, title, author): self.title = title self.author = author # A User object Use a :class:`Nested ` field to represent the relationship, passing in a nested schema class. .. code-block:: python :emphasize-lines: 10 from marshmallow import Schema, fields, pprint 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 user = User(name="Monty", email="monty@python.org") blog = Blog(title="Something Completely Different", author=user) result, errors = 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, you must set ``many=True``. .. code-block:: python collaborators = fields.Nested(UserSchema, many=True) .. _specifying-nested-fields: Specifying Which Fields to Nest ------------------------------- You can explicitly specify which attributes of the nested objects you want to serialize with the ``only`` argument. .. code-block:: python :emphasize-lines: 3 class BlogSchema2(Schema): title = fields.String() author = fields.Nested(UserSchema, only=["email"]) schema = BlogSchema2() result, errors = schema.dump(blog) pprint(result) # { # 'title': u'Something Completely Different', # 'author': {'email': u'monty@python.org'} # } You can represent the attributes of deeply nested objects using dot delimiters. .. code-block:: python :emphasize-lines: 5 class SiteSchema(Schema): blog = fields.Nested(BlogSchema2) schema = SiteSchema(only=['blog.author.email']) result, errors = schema.dump(site) pprint(result) # { # 'blog': { # 'author': {'email': u'monty@python.org'} # } # } .. note:: If you pass in a string field name to ``only``, only a single value (or flat list of values if ``many=True``) will be (de)serialized. .. code-block:: python :emphasize-lines: 4, 11, 18 class UserSchema(Schema): name = fields.String() email = fields.Email() friends = fields.Nested('self', only='name', many=True) # ... create ``user`` ... serialized_data, errors = UserSchema().dump(user) pprint(serialized_data) # { # "name": "Steve", # "email": "steve@example.com", # "friends": ["Mike", "Joe"] # } deserialized_data, errors = UserSchema().load(result) pprint(deserialized_data) # { # "name": "Steve", # "email": "steve@example.com", # "friends": [{"name": "Mike"}, {"name": "Joe"}] # } You can also exclude fields by passing in an ``exclude`` list. This argument also allows representing the attributes of deeply nested objects using dot delimiters. .. _two-way-nesting: Two-way Nesting --------------- If you have two objects that nest each other, you can refer to a nested schema by its class name. This allows you to nest Schemas that have not yet been defined. For example, a representation of an ``Author`` model might include the books that have a foreign-key (many-to-one) relationship to it. Correspondingly, a representation of a ``Book`` will include its author representation. .. code-block:: python :emphasize-lines: 4 class AuthorSchema(Schema): # Make sure to use the 'only' or 'exclude' params # to avoid infinite recursion books = fields.Nested('BookSchema', many=True, exclude=('author', )) class Meta: fields = ('id', 'name', 'books') class BookSchema(Schema): author = fields.Nested(AuthorSchema, only=('id', 'name')) class Meta: fields = ('id', 'title', '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, errors = BookSchema().dump(book) pprint(book_result, indent=2) # { # "id": 124, # "title": "As I Lay Dying", # "author": { # "id": 8, # "name": "William Faulkner" # } # } author_result, errors = AuthorSchema().dump(author) pprint(author_result, indent=2) # { # "id": 8, # "name": "William Faulkner", # "books": [ # { # "id": 124, # "title": "As I Lay Dying" # } # ] # } .. note:: If you need to, you can also pass the full, module-qualified path to `fields.Nested`. :: books = fields.Nested('path.to.BookSchema', many=True, exclude=('author', )) .. _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 ``"self"`` (with quotes) to the :class:`Nested ` constructor. .. code-block:: python :emphasize-lines: 4,6 class UserSchema(Schema): name = fields.String() email = fields.Email() friends = fields.Nested('self', many=True) # Use the 'exclude' argument to avoid infinite recursion employer = fields.Nested('self', exclude=('employer', ), default=None) 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.data, 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 :ref:`Custom Fields ` page. - Need to add schema-level validation, post-processing, or error handling behavior? See the :ref:`Extending Schemas ` page. - For example applications using marshmallow, check out the :ref:`Examples ` page. marshmallow-3.0.0b3/docs/quickstart.rst000066400000000000000000000414671314633611600201610ustar00rootroot00000000000000.. _quickstart: .. module:: marshmallow Quickstart ========== 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 import datetime as dt class User(object): def __init__(self, name, email): self.name = name self.email = email self.created_at = dt.datetime.now() def __repr__(self): return ''.format(self=self) 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 :ref:`API Docs `. Serializing Objects ("Dumping") ------------------------------- Serialize objects by passing them to your schema's :meth:`dump ` method, which returns the formatted result (as well as a dictionary of validation errors, which we'll :ref:`revisit later `). .. code-block:: python from marshmallow import pprint user = User(name="Monty", email="monty@python.org") schema = UserSchema() result = schema.dump(user) pprint(result.data) # {"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) pprint(json_result.data) # '{"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).data # {"name": "Monty Python", "email": "monty@python.org"} You can also exclude fields by passing in the ``exclude`` parameter. Deserializing Objects ("Loading") --------------------------------- The opposite of the :meth:`dump ` method is the :meth:`load ` method, which deserializes an input dictionary to an application-level data structure. By default, :meth:`load ` will return a dictionary of field names mapped to the deserialized values. .. code-block:: python from pprint import pprint user_data = { 'created_at': '2014-08-11T05:26:03.869245', 'email': u'ken@yahoo.com', 'name': u'Ken' } schema = UserSchema() result = schema.load(user_data) pprint(result.data) # {'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 as its only parameter. .. code-block:: python :emphasize-lines: 8-10 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): return User(**data) Now, the :meth:`load ` method will return a ``User`` object. .. code-block:: python user_data = { 'name': 'Ronnie', 'email': 'ronnie@stones.com' } schema = UserSchema() result = schema.load(user_data) result.data # => Handling Collections of Objects ------------------------------- Iterable collections of objects are also serializable and deserializable. Just set ``many=True``. .. code-block:: python :emphasize-lines: 3,4 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) result.data # [{'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 ---------- :meth:`Schema.load` (and its JSON-decoding counterpart, :meth:`Schema.loads`) returns a dictionary of validation errors as the second element of its return value. Some fields, such as the :class:`Email ` and :class:`URL ` fields, have built-in validation. .. code-block:: python data, errors = UserSchema().load({'email': 'foo'}) errors # => {'email': ['"foo" is not a valid email address.']} # OR, equivalently result = UserSchema().load({'email': 'foo'}) result.errors # => {'email': ['"foo" is not a valid email address.']} When validating a collection, the errors dictionary will be keyed on the indices of invalid items. .. code-block:: python 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) result.errors # {1: {'email': ['"invalid" is not a valid email address.']}, # 3: {'name': ['Missing data for required field.']}} You can perform additional validation for a field by passing it a ``validate`` callable (function, lambda, or object with ``__call__`` defined). .. code-block:: python :emphasize-lines: 4 class ValidatedUserSchema(UserSchema): # NOTE: This is a contrived example. # You could use marshmallow.validate.Range instead of an anonymous function here age = fields.Number(validate=lambda n: 18 <= n <= 40) in_data = {'name': 'Mick', 'email': 'mick@stones.com', 'age': 71} result = ValidatedUserSchema().load(in_data) result.errors # => {'age': ['Validator (71.0) is False']} Validation functions either return a boolean or raise a :exc:`ValidationError`. If a :exc:`ValidationError ` is raised, its message is stored when validation fails. .. code-block:: python :emphasize-lines: 7,10,14 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} result, errors = ItemSchema().load(in_data) errors # => {'quantity': ['Quantity must not be greater than 30.']} .. note:: If you have multiple validations to perform, you may also pass a collection (list, tuple, generator) of callables. .. note:: :meth:`Schema.dump` also returns a dictionary of errors, which will include any ``ValidationErrors`` raised during serialization. However, ``required``, ``allow_none``, ``validate``, `@validates `, and `@validates_schema ` only apply during deserialization. Field Validators as Methods +++++++++++++++++++++++++++ It is often 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.') ``strict`` Mode +++++++++++++++ If you set ``strict=True`` in either the Schema constructor or as a ``class Meta`` option, an error will be raised when invalid data are passed in. You can access the dictionary of validation errors from the `ValidationError.messages ` attribute. .. code-block:: python from marshmallow import ValidationError try: UserSchema(strict=True).load({'email': 'foo'}) except ValidationError as err: print(err.messages)# => {'email': ['"foo" is not a valid email address.']} .. seealso:: You can register a custom error handler function for a schema by overriding the :func:`handle_error ` method. See the :ref:`Extending Schemas ` page for more info. .. seealso:: Need schema-level validation? See the :ref:`Extending Schemas ` page. Required Fields +++++++++++++++ You can make a field required by passing ``required=True``. An error will be stored if the the value is missing from the input to :meth:`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 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() data, errors = UserSchema().load({'email': 'foo@bar.com'}) errors # {'name': ['Missing data for required field.'], # 'age': ['Age is required.'], # 'city': {'message': 'City required', 'code': 400}} Partial Loading +++++++++++++++ When using the same schema in multiple places, you may only want to check required fields some of the time when deserializing by specifying them in ``partial``. .. code-block:: python :emphasize-lines: 5,6 class UserSchema(Schema): name = fields.String(required=True) age = fields.Integer(required=True) data, errors = UserSchema().load({'age': 42}, partial=('name',)) # OR UserSchema(partial=('name',)).load({'age': 42}) data, errors # => ({'age': 42}, {}) Or you can ignore missing fields entirely by setting ``partial=True``. .. code-block:: python :emphasize-lines: 5,6 class UserSchema(Schema): name = fields.String(required=True) age = fields.Integer(required=True) data, errors = UserSchema().load({'age': 42}, partial=True) # OR UserSchema(partial=True).load({'age': 42}) data, errors # => ({'age': 42}, {}) Schema.validate +++++++++++++++ If you only need to validate input data (without deserializing to an object), you can use :meth:`Schema.validate`. .. code-block:: python errors = UserSchema().validate({'name': 'Ronnie', 'email': 'invalid-email'}) errors # {'email': ['"invalid-email" is not a valid email address.']} Specifying Attribute Names -------------------------- By default, `Schemas` will marshal the object attributes that are identical to the schema's field names. However, you may want to have different field and attribute names. In this case, you can explicitly specify which attribute names to use. .. code-block:: python :emphasize-lines: 3,4,11,12 class UserSchema(Schema): name = fields.String() email_addr = fields.String(attribute="email") date_created = fields.DateTime(attribute="created_at") user = User('Keith', email='keith@stones.com') ser = UserSchema() result, errors = ser.dump(user) pprint(result) # {'name': 'Keith', # 'email_addr': 'keith@stones.com', # 'date_created': '2014-08-17T14:58:57.600623+00:00'} Specifying Deserialization Keys ------------------------------- By default `Schemas` will unmarshal an input dictionary to an output dictionary whose keys are identical to the field names. However, if you are consuming data that does not exactly match your schema, you can specify additional keys to load values by passing the `load_from` argument. .. code-block:: python :emphasize-lines: 2,3,11,12 class UserSchema(Schema): name = fields.String() email = fields.Email(load_from='emailAddress') data = { 'name': 'Mike', 'emailAddress': 'foo@bar.com' } s = UserSchema() result, errors = s.load(data) #{'name': u'Mike', # 'email': 'foo@bar.com'} .. _meta_options: Specifying Serialization Keys ------------------------------- If you want to marshal a field to a different key than the field name you can use `dump_to`, which is analogous to `load_from`. .. code-block:: python :emphasize-lines: 2,3,11,12 class UserSchema(Schema): name = fields.String(dump_to='TheName') email = fields.Email(load_from='CamelCasedEmail', dump_to='CamelCasedEmail') data = { 'name': 'Mike', 'email': 'foo@bar.com' } s = UserSchema() result, errors = s.dump(data) #{'TheName': u'Mike', # 'CamelCasedEmail': 'foo@bar.com'} Refactoring: Implicit Field Creation ------------------------------------ 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 *class Meta* paradigm allows you to specify which attributes you want to serialize. 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 :emphasize-lines: 4,5 # Refactored schema 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") Ordering Output --------------- For some use cases, it may be useful to maintain field ordering of serialized output. To enable ordering, set the ``ordered`` option to `True`. This will instruct marshmallow to serialize data to a `collections.OrderedDict`. .. code-block:: python :emphasize-lines: 7 from collections import OrderedDict class UserSchema(Schema): uppername = fields.Function(lambda obj: obj.name.upper()) class Meta: fields = ("name", "email", "created_at", "uppername") ordered = True u = User('Charlie', 'charlie@stones.com') schema = UserSchema() result = schema.dump(u) assert isinstance(result.data, OrderedDict) # marshmallow's pprint function maintains order pprint(result.data, indent=2) # { # "name": "Charlie", # "email": "charlie@stones.com", # "created_at": "2014-10-30T08:27:48.515735+00:00", # "uppername": "CHARLIE" # } "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) Next Steps ---------- - Need to represent relationships between objects? See the :ref:`Nesting Schemas ` page. - Want to create your own field type? See the :ref:`Custom Fields ` page. - Need to add schema-level validation, post-processing, or error handling behavior? See the :ref:`Extending Schemas ` page. - For example applications using marshmallow, check out the :ref:`Examples ` page. marshmallow-3.0.0b3/docs/requirements.txt000066400000000000000000000001171314633611600205040ustar00rootroot00000000000000sphinx sphinx-issues==0.2.0 git+https://github.com/sloria/alabaster.git@sloria marshmallow-3.0.0b3/docs/upgrading.rst000066400000000000000000000710471314633611600177440ustar00rootroot00000000000000 .. _upgrading: Upgrading to Newer Releases =========================== This section documents migration paths to new releases. Upgrading to 3.0 ++++++++++++++++ Python compatibility ******************** The marshmallow 3.x series supports Python 2.7, 3.4, 3.5, and 3.6. Python 2.6 and 3.3 are no longer supported. 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): # ... # 3.x class MySchema(Schema): def get_attribute(self, obj, attr, default): # ... ``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.data # => {} # 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.data # => {} 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}).data # => {'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}).data # => {'z': 123, 'y': 2, 'x': 1} Schema-level validators are skipped when field validation fails *************************************************************** By default, schema validator methods decorated by `validates_schema ` will not be executed 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(strict=True) # 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(strict=True) 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 do not 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 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) 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()) Similiarly, 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 :ref:`Extending Schemas ` 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 :emphasize-lines: 3,14 from marshmallow import exceptions as exc schema = BandMemberSchema(strict=True) # 1.0 try: schema.load({'email': 'invalid-email'}) except exc.UnmarshallingError as err: # ... # 2.0 try: schema.load({'email': 'invalid-email'}) except exc.ValidationError as 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 :emphasize-lines: 6 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 :ref:`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` . - :class:`Serializer` has been renamed to :class:`Schema`, but you can still import `marshmallow.Serializer` (which is aliased to :class:`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 :ref:`Changelog ` for a more complete listing of added features, bugfixes and breaking changes. marshmallow-3.0.0b3/docs/why.rst000066400000000000000000000126031314633611600165640ustar00rootroot00000000000000.. _why: Why 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 :ref:`class Meta ` paradigm. Configuration options can be overriden 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 allows for powerful means for configuring and extending schemas, such as adding :ref:`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 outputs 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) players = fields.Nested(PlayerSchema, many=True) score = fields.Nested(ScoreSchema) last_changed = fields.DateTime(format='rfc') class Meta: additional = ('title', 'date_created', 'type', 'is_active') # 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: http://www.django-rest-framework.org/ .. _Flask-RESTful: http://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 :ref:`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.0.0b3/examples/000077500000000000000000000000001314633611600161075ustar00rootroot00000000000000marshmallow-3.0.0b3/examples/flask_example.py000066400000000000000000000101301314633611600212670ustar00rootroot00000000000000import datetime from flask import Flask, jsonify, request from flask.ext.sqlalchemy import SQLAlchemy from sqlalchemy.exc import IntegrityError from marshmallow import Schema, fields, ValidationError, pre_load app = Flask(__name__) app.config["SQLALCHEMY_DATABASE_URI"] = 'sqlite:////tmp/quotes.db' db = SQLAlchemy(app) ##### MODELS ##### class Author(db.Model): id = db.Column(db.Integer, primary_key=True) first = db.Column(db.String(80)) last = db.Column(db.String(80)) class Quote(db.Model): id = db.Column(db.Integer, primary_key=True) content = db.Column(db.String, nullable=False) author_id = db.Column(db.Integer, db.ForeignKey("author.id")) author = db.relationship("Author", backref=db.backref("quotes", lazy="dynamic")) posted_at = db.Column(db.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 "{}, {}".format(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): author_name = data.get('author') if author_name: first, last = author_name.split(' ') author_dict = 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 jsonify({'authors': result.data}) @app.route("/authors/") def get_author(pk): try: author = Author.query.get(pk) except IntegrityError: return jsonify({"message": "Author could not be found."}), 400 author_result = author_schema.dump(author) quotes_result = quotes_schema.dump(author.quotes.all()) return jsonify({'author': author_result.data, 'quotes': quotes_result.data}) @app.route('/quotes/', methods=['GET']) def get_quotes(): quotes = Quote.query.all() result = quotes_schema.dump(quotes) return jsonify({"quotes": result.data}) @app.route("/quotes/") def get_quote(pk): try: quote = Quote.query.get(pk) except IntegrityError: return jsonify({"message": "Quote could not be found."}), 400 result = quote_schema.dump(quote) return jsonify({"quote": result.data}) @app.route("/quotes/", methods=["POST"]) def new_quote(): json_data = request.get_json() if not json_data: return jsonify({'message': 'No input data provided'}), 400 # Validate and deserialize input data, errors = quote_schema.load(json_data) if errors: return jsonify(errors), 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.utcnow() ) db.session.add(quote) db.session.commit() result = quote_schema.dump(Quote.query.get(quote.id)) return jsonify({"message": "Created new quote.", "quote": result.data}) if __name__ == '__main__': db.create_all() app.run(debug=True, port=5000) marshmallow-3.0.0b3/examples/peewee_example.py000066400000000000000000000125441314633611600214540ustar00rootroot00000000000000import datetime as dt from functools import wraps from flask import Flask, request, g, jsonify import peewee as pw from marshmallow import Schema, fields, validate, pre_load, post_dump app = Flask(__name__) db = pw.SqliteDatabase('/tmp/todo.db') ###### MODELS ##### class BaseModel(pw.Model): """Base model class. All descendants share the same database.""" class Meta: database = db class User(BaseModel): email = pw.CharField(max_length=80, unique=True) password = pw.CharField() joined_on = pw.DateTimeField() class Todo(BaseModel): content = pw.TextField() is_done = pw.BooleanField(default=False) user = pw.ForeignKeyField(User) posted_on = pw.DateTimeField() class Meta: order_by = ('-posted_on', ) def create_tables(): db.connect() User.create_table(True) Todo.create_table(True) ##### SCHEMAS ##### class UserSchema(Schema): id = fields.Int(dump_only=True) email = fields.Str(required=True, validate=validate.Email(error='Not a valid email address')) password = fields.Str(required=True, validate=[validate.Length(min=6, max=36)], load_only=True) joined_on = fields.DateTime(dump_only=True) # Clean up data @pre_load def process_input(self, data): data['email'] = data['email'].lower().strip() return data # We add a post_dump hook to add an envelope to responses @post_dump(pass_many=True) def wrap(self, data, many): key = 'users' if many else 'user' return { key: data } class TodoSchema(Schema): id = fields.Int(dump_only=True) done = fields.Boolean(attribute='is_done', missing=False) user = fields.Nested(UserSchema, exclude=('joined_on', 'password'), dump_only=True) content = fields.Str(required=True) posted_on = fields.DateTime(dump_only=True) # Again, add an envelope to responses @post_dump(pass_many=True) def wrap(self, data, many): key = 'todos' if many else 'todo' return { key: data } # We use make_object to create a new Todo from validated data def make_object(self, data): if not data: return None return Todo(content=data['content'], is_done=data['is_done'], posted_on=dt.datetime.utcnow()) user_schema = UserSchema() todo_schema = TodoSchema() todos_schema = TodoSchema(many=True) ###### HELPERS ###### def check_auth(email, password): """Check if a username/password combination is valid. """ try: user = User.get(User.email == email) except User.DoesNotExist: return False return password == user.password def requires_auth(f): @wraps(f) def decorated(*args, **kwargs): auth = request.authorization if not auth or not check_auth(auth.username, auth.password): resp = jsonify({"message": "Please authenticate."}) resp.status_code = 401 resp.headers['WWW-Authenticate'] = 'Basic realm="Example"' return resp kwargs['user'] = User.get(User.email == auth.username) return f(*args, **kwargs) return decorated # Ensure a separate connection for each thread @app.before_request def before_request(): g.db = db g.db.connect() @app.after_request def after_request(response): g.db.close() return response #### API ##### @app.route("/register", methods=["POST"]) def register(): json_input = request.get_json() data, errors = user_schema.load(json_input) if errors: return jsonify({'errors': errors}), 422 try: # Use get to see if user already to exists User.get(User.email == data['email']) except User.DoesNotExist: user = User.create(email=data['email'], joined_on=dt.datetime.now(), password=data['password']) message = "Successfully created user: {0}".format(user.email) else: return jsonify({'errors': 'That email address is already in the database'}), 400 data, _ = user_schema.dump(user) data['message'] = message return jsonify(data), 201 @app.route("/todos/", methods=['GET']) def get_todos(): todos = Todo.select().order_by(Todo.posted_on.asc()) # Get all todos result = todos_schema.dump(list(todos)) return jsonify(result.data) @app.route("/todos/") def get_todo(pk): todo = Todo.get(Todo.id == pk) if not todo: return jsonify({'errors': 'Todo could not be find'}), 404 result = todo_schema.dump(todo) return jsonify(result.data) @app.route("/todos//toggle", methods=["POST", "PUT"]) def toggledone(pk): try: todo = Todo.get(Todo.id == pk) except Todo.DoesNotExist: return jsonify({"message": "Todo could not be found"}), 404 status = not todo.is_done update_query = todo.update(is_done=status) update_query.execute() result = todo_schema.dump(todo) return jsonify(result.data) @app.route('/todos/', methods=["POST"]) @requires_auth def new_todo(user): json_input = request.get_json() todo, errors = todo_schema.load(json_input) if errors: return jsonify({'errors': errors}), 422 todo.user = user todo.save() result = todo_schema.dump(todo) return jsonify(result.data) if __name__ == '__main__': create_tables() app.run(port=5000, debug=True) marshmallow-3.0.0b3/examples/textblob_example.py000066400000000000000000000015151314633611600220210ustar00rootroot00000000000000from bottle import route, request, run from textblob import TextBlob from marshmallow import Schema, fields class BlobSchema(Schema): polarity = fields.Float() subjectivity = fields.Float() chunks = fields.List(fields.String, attribute="noun_phrases") tags = fields.Raw() discrete_sentiment = fields.Method("get_discrete_sentiment") word_count = fields.Function(lambda obj: len(obj.words)) def get_discrete_sentiment(self, obj): if obj.polarity > 0.1: return 'positive' elif obj.polarity < -0.1: return 'negative' else: return 'neutral' blob_schema = BlobSchema() @route("/api/v1/analyze", method="POST") def analyze(): blob = TextBlob(request.json['text']) result = blob_schema.dump(blob) return result.data run(reloader=True, port=5000) marshmallow-3.0.0b3/marshmallow/000077500000000000000000000000001314633611600166175ustar00rootroot00000000000000marshmallow-3.0.0b3/marshmallow/__init__.py000066400000000000000000000013131314633611600207260ustar00rootroot00000000000000# -*- coding: utf-8 -*- from __future__ import absolute_import from marshmallow.schema import ( Schema, SchemaOpts, MarshalResult, UnmarshalResult, ) from . import fields from marshmallow.decorators import ( pre_dump, post_dump, pre_load, post_load, validates, validates_schema ) from marshmallow.utils import pprint, missing from marshmallow.exceptions import ValidationError __version__ = '3.0.0b3' __author__ = 'Steven Loria' __all__ = [ 'Schema', 'SchemaOpts', 'fields', 'validates', 'validates_schema', 'pre_dump', 'post_dump', 'pre_load', 'post_load', 'pprint', 'MarshalResult', 'UnmarshalResult', 'ValidationError', 'missing', ] marshmallow-3.0.0b3/marshmallow/base.py000066400000000000000000000020011314633611600200740ustar00rootroot00000000000000# -*- coding: utf-8 -*- """Abstract base classes. These are necessary to avoid circular imports between core.py and fields.py. """ import copy class FieldABC(object): """Abstract base class from which all Field classes inherit. """ parent = None name = None def serialize(self, attr, obj, accessor=None): raise NotImplementedError def deserialize(self, value): raise NotImplementedError def _serialize(self, value, attr, obj): raise NotImplementedError def _deserialize(self, value, attr, ob): raise NotImplementedError def __deepcopy__(self, memo): ret = copy.copy(self) return ret class SchemaABC(object): """Abstract base class from which all Schemas inherit.""" def dump(self, obj): raise NotImplementedError def dumps(self, obj, *args, **kwargs): raise NotImplementedError def load(self, data): raise NotImplementedError def loads(self, data): raise NotImplementedError marshmallow-3.0.0b3/marshmallow/class_registry.py000066400000000000000000000045411314633611600222320ustar00rootroot00000000000000# -*- coding: utf-8 -*- """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. """ from __future__ import unicode_literals from marshmallow.exceptions import RegistryError # { # : # : # } _registry = {} def register(classname, cls): """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 = '.'.join([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) else: _registry[classname] = [cls] # Also register the full path _registry.setdefault(fullpath, []).append(cls) return None def get_class(classname, all=False): """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: raise RegistryError('Class with name {0!r} was not found. You may need ' 'to import the class.'.format(classname)) if len(classes) > 1: if all: return _registry[classname] raise RegistryError('Multiple classes with name {0!r} ' 'were found. Please use the full, ' 'module-qualified path.'.format(classname)) else: return _registry[classname][0] marshmallow-3.0.0b3/marshmallow/compat.py000066400000000000000000000023431314633611600204560ustar00rootroot00000000000000# -*- coding: utf-8 -*- import sys import itertools import functools import inspect PY2 = int(sys.version_info[0]) == 2 if PY2: import urlparse urlparse = urlparse text_type = unicode binary_type = str string_types = (str, unicode) unicode = unicode basestring = basestring iterkeys = lambda d: d.iterkeys() itervalues = lambda d: d.itervalues() iteritems = lambda d: d.iteritems() zip_longest = itertools.izip_longest else: import urllib.parse urlparse = urllib.parse text_type = str binary_type = bytes string_types = (str,) unicode = str basestring = (str, bytes) iterkeys = lambda d: d.keys() itervalues = lambda d: d.values() iteritems = lambda d: d.items() zip_longest = itertools.zip_longest # From six def with_metaclass(meta, *bases): """Create a base class with a metaclass.""" # This requires a bit of explanation: the basic idea is to make a dummy # metaclass for one level of class instantiation that replaces itself with # the actual metaclass. class metaclass(meta): # noqa def __new__(cls, name, this_bases, d): return meta(name, bases, d) return type.__new__(metaclass, 'temporary_class', (), {}) marshmallow-3.0.0b3/marshmallow/decorators.py000066400000000000000000000140451314633611600213420ustar00rootroot00000000000000# -*- coding: utf-8 -*- """Decorators for registering schema pre-processing and post-processing methods. These should be imported from the top-level `marshmallow` module. 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): item['email'] = item['email'].lower().strip() return item @pre_load(pass_many=True) def remove_envelope(self, data, many): namespace = 'results' if many else 'result' return data[namespace] @post_dump(pass_many=True) def add_envelope(self, data, many): namespace = 'results' if many else 'result' return {namespace: data} @validates_schema def validate_email(self, data): if len(data['email']) < 3: raise ValidationError('Email must be more than 3 characters', 'email') @validates('age') def validate_age(self, data): 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 unicode_literals import functools PRE_DUMP = 'pre_dump' POST_DUMP = 'post_dump' PRE_LOAD = 'pre_load' POST_LOAD = 'post_load' VALIDATES = 'validates' VALIDATES_SCHEMA = 'validates_schema' def validates(field_name): """Register a field validator. :param str field_name: Name of the field that the method validates. """ return tag_processor(VALIDATES, None, False, field_name=field_name) def validates_schema(fn=None, pass_many=False, pass_original=False, skip_on_field_errors=True): """Register a schema-level validator. By default, receives a single object at a time, regardless of whether ``many=True`` is passed to the `Schema`. If ``pass_many=True``, the raw data (which may be a collection) and the value for ``many`` 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`. """ return tag_processor(VALIDATES_SCHEMA, fn, pass_many, pass_original=pass_original, skip_on_field_errors=skip_on_field_errors) def pre_dump(fn=None, pass_many=False): """Register a method to invoke before serializing an object. The method receives the object to be serialized and returns the processed object. By default, receives a single object at a time, regardless of whether ``many=True`` is passed to the `Schema`. If ``pass_many=True``, the raw data (which may be a collection) and the value for ``many`` is passed. """ return tag_processor(PRE_DUMP, fn, pass_many) def post_dump(fn=None, pass_many=False, pass_original=False): """Register a method to invoke after serializing an object. The method receives the serialized object and returns the processed object. By default, receives a single object at a time, transparently handling the ``many`` argument passed to the Schema. If ``pass_many=True``, the raw data (which may be a collection) and the value for ``many`` is passed. """ return tag_processor(POST_DUMP, fn, pass_many, pass_original=pass_original) def pre_load(fn=None, pass_many=False): """Register a method to invoke before deserializing an object. The method receives the data to be deserialized and returns the processed data. By default, receives a single datum at a time, transparently handling the ``many`` argument passed to the Schema. If ``pass_many=True``, the raw data (which may be a collection) and the value for ``many`` is passed. """ return tag_processor(PRE_LOAD, fn, pass_many) def post_load(fn=None, pass_many=False, pass_original=False): """Register a method to invoke after deserializing an object. The method receives the deserialized data and returns the processed data. By default, receives a single datum at a time, transparently handling the ``many`` argument passed to the Schema. If ``pass_many=True``, the raw data (which may be a collection) and the value for ``many`` is passed. """ return tag_processor(POST_LOAD, fn, pass_many, pass_original=pass_original) def tag_processor(tag_name, fn, pass_many, **kwargs): """Tags decorated processor function to be picked up later. .. note:: Currently ony 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( tag_processor, tag_name, pass_many=pass_many, **kwargs ) # Set a marshmallow_tags attribute instead of wrapping in some class, # because I still want this to end up as a normal (unbound) method. try: marshmallow_tags = fn.__marshmallow_tags__ except AttributeError: fn.__marshmallow_tags__ = marshmallow_tags = set() # Also save the kwargs for the tagged function on # __marshmallow_kwargs__, keyed by (, ) try: marshmallow_kwargs = fn.__marshmallow_kwargs__ except AttributeError: fn.__marshmallow_kwargs__ = marshmallow_kwargs = {} marshmallow_tags.add((tag_name, pass_many)) marshmallow_kwargs[(tag_name, pass_many)] = kwargs return fn marshmallow-3.0.0b3/marshmallow/exceptions.py000066400000000000000000000041301314633611600213500ustar00rootroot00000000000000# -*- coding: utf-8 -*- """Exception classes for marshmallow-related errors.""" from marshmallow.compat import basestring class MarshmallowError(Exception): """Base class for all marshmallow-related errors.""" pass class ValidationError(MarshmallowError): """Raised when validation fails on a field. Validators and custom fields should raise this exception. :param message: An error message, list of error messages, or dict of error messages. :param list field_names: Field names to store the error on. If `None`, the error is stored in its default location. :param list fields: `Field` objects to which the error applies. """ def __init__(self, message, field_names=None, fields=None, data=None, valid_data=None, **kwargs): if not isinstance(message, dict) and not isinstance(message, list): messages = [message] else: messages = message #: String, list, or dictionary of error messages. #: If a `dict`, the keys will be field names and the values will be lists of #: messages. self.messages = messages #: List of field objects which failed validation. self.fields = fields if isinstance(field_names, basestring): #: List of field_names which failed validation. self.field_names = [field_names] else: # fields is a list or None self.field_names = field_names or [] #: The raw input data. self.data = data #: The valid, (de)serialized data. self.valid_data = valid_data self.kwargs = kwargs MarshmallowError.__init__(self, message) def normalized_messages(self, no_field_name="_schema"): if isinstance(self.messages, dict): return self.messages if len(self.field_names) == 0: return {no_field_name: self.messages} return dict((name, self.messages) for name in self.field_names) class RegistryError(NameError): """Raised when an invalid operation is performed on the serializer class registry. """ pass marshmallow-3.0.0b3/marshmallow/fields.py000077500000000000000000001377651314633611600204650ustar00rootroot00000000000000# -*- coding: utf-8 -*- """Field classes for various types of data.""" from __future__ import absolute_import, unicode_literals import collections import datetime as dt import numbers import uuid import warnings import decimal from operator import attrgetter from marshmallow import validate, utils, class_registry from marshmallow.base import FieldABC, SchemaABC from marshmallow.utils import missing as missing_ from marshmallow.compat import text_type, basestring from marshmallow.exceptions import ValidationError from marshmallow.validate import Validator __all__ = [ 'Field', 'Raw', 'Nested', 'Dict', 'List', 'String', 'UUID', 'Number', 'Integer', 'Decimal', 'Boolean', 'FormattedString', 'Float', 'DateTime', 'LocalDateTime', 'Time', 'Date', 'TimeDelta', 'Url', 'URL', 'Email', 'Method', 'Function', 'Str', 'Bool', 'Int', 'Constant', ] MISSING_ERROR_MESSAGE = ( 'ValidationError raised by `{class_name}`, but error key `{key}` does ' 'not exist in the `error_messages` dictionary.' ) _RECURSIVE_NESTED = 'self' class Field(FieldABC): """Basic field from which other fields should extend. It applies no formatting by default, and should only be used in cases where data does not need to be formatted before being serialized or deserialized. On error, the name of the field will be returned. :param 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 str attribute: The name of the attribute to get the value from. If `None`, assumes the attribute has the same name as the field. :param str load_from: Additional key to look for when deserializing. Will only be checked if the field's name is not found on the input dictionary. If checked, it will return this parameter on error. :param str dump_to: Field name to use as a key when serializing. :param callable 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 ``missing=None`` and ``allow_none`` is unset, will default to ``True``. Otherwise, the default is ``False``. :param bool load_only: If `True` skip this field during serialization, otherwise its value will be present in the serialized data. :param bool 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 missing: Default deserialization value for the field if the field is not found in the input data. May be a value or a callable. :param dict error_messages: Overrides for `Field.default_error_messages`. :param metadata: Extra arguments to be stored as metadata. .. versionchanged:: 2.0.0 Removed `error` parameter. Use ``error_messages`` instead. .. versionchanged:: 2.0.0 Added `allow_none` parameter, which makes validation/deserialization of `None` consistent across fields. .. versionchanged:: 2.0.0 Added `load_only` and `dump_only` parameters, which allow field skipping during the (de)serialization process. .. versionchanged:: 2.0.0 Added `missing` parameter, which indicates the value for a field if the field is not found during deserialization. .. versionchanged:: 2.0.0 ``default`` value is only used if explicitly set. Otherwise, missing values inputs are excluded from serialized output. """ # Some fields, such as Method fields and Function fields, are not expected # to exists as attributes on the objects to serialize. Set this to False # for those fields _CHECK_ATTRIBUTE = True _creation_index = 0 # Used for sorting #: Default error messages for various kinds of errors. The keys in this dictionary #: are passed to `Field.fail`. The values are error messages passed to #: :exc:`marshmallow.ValidationError`. default_error_messages = { 'required': 'Missing data for required field.', 'type': 'Invalid input type.', # used by Unmarshaller 'null': 'Field may not be null.', 'validator_failed': 'Invalid value.' } def __init__(self, default=missing_, attribute=None, load_from=None, dump_to=None, error=None, validate=None, required=False, allow_none=None, load_only=False, dump_only=False, missing=missing_, error_messages=None, **metadata): self.default = default self.attribute = attribute self.load_from = load_from # this flag is used by Unmarshaller self.dump_to = dump_to # this flag is used by Marshaller self.validate = validate if utils.is_iterable_but_not_string(validate): if not utils.is_generator(validate): self.validators = validate else: self.validators = list(validate) elif callable(validate): self.validators = [validate] elif validate is None: self.validators = [] else: raise ValueError("The 'validate' parameter must be a callable " "or a collection of callables.") self.required = required # If missing=None, None should be considered valid by default if allow_none is None: if missing is None: self.allow_none = True else: self.allow_none = False else: self.allow_none = allow_none self.load_only = load_only self.dump_only = dump_only self.missing = missing self.metadata = metadata self._creation_index = Field._creation_index Field._creation_index += 1 # Collect default error message from self and parent classes messages = {} for cls in reversed(self.__class__.__mro__): messages.update(getattr(cls, 'default_error_messages', {})) messages.update(error_messages or {}) self.error_messages = messages def __repr__(self): return ('' .format(ClassName=self.__class__.__name__, self=self)) def get_value(self, obj, attr, accessor=None, default=missing_): """Return the value for a given key from an object.""" # NOTE: Use getattr instead of direct attribute access here so that # subclasses aren't required to define `attribute` member attribute = getattr(self, 'attribute', None) accessor_func = accessor or utils.get_value check_key = attr if attribute is None else attribute return accessor_func(obj, check_key, default) def _validate(self, value): """Perform validation on ``value``. Raise a :exc:`ValidationError` if validation does not succeed. """ errors = [] kwargs = {} for validator in self.validators: try: r = validator(value) if not isinstance(validator, Validator) and r is False: self.fail('validator_failed') 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) # Hat tip to django-rest-framework. def fail(self, key, **kwargs): """A helper method that simply raises a `ValidationError`. """ try: msg = self.error_messages[key] except KeyError: class_name = self.__class__.__name__ msg = MISSING_ERROR_MESSAGE.format(class_name=class_name, key=key) raise AssertionError(msg) if isinstance(msg, basestring): msg = msg.format(**kwargs) raise ValidationError(msg) def _validate_missing(self, value): """Validate missing values. Raise a :exc:`ValidationError` if `value` should be considered missing. """ if value is missing_: if hasattr(self, 'required') and self.required: self.fail('required') if value is None: if hasattr(self, 'allow_none') and self.allow_none is not True: self.fail('null') def serialize(self, attr, obj, accessor=None): """Pulls the value for the given key from the object, applies the field's formatting and returns the result. :param str attr: The attibute or key to get from the object. :param str obj: The object to pull the key from. :param callable accessor: Function used to pull values from ``obj``. :raise ValidationError: In case of formatting problem """ if self._CHECK_ATTRIBUTE: value = self.get_value(obj, attr, accessor=accessor) if value is missing_: if hasattr(self, 'default'): if callable(self.default): return self.default() else: return self.default else: value = None return self._serialize(value, attr, obj) def deserialize(self, value, attr=None, data=None): """Deserialize ``value``. :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 getattr(self, 'allow_none', False) is True and value is None: return None output = self._deserialize(value, attr, data) self._validate(output) return output # Methods for concrete classes to override. def _add_to_schema(self, field_name, schema): """Update field with values from its parent schema. Called by :meth:`__set_field_attrs `. :param str field_name: Field name set in schema. :param Schema schema: Parent schema. """ self.parent = self.parent or schema self.name = self.name or field_name def _serialize(self, value, attr, obj): """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): if not value: return '' return unicode(value).title() :param value: The value to be serialized. :param str attr: The attribute or key on the object to be serialized. :param object obj: The object the value was pulled from. :raise ValidationError: In case of formatting or validation failure. :return: The serialized value """ return value def _deserialize(self, value, attr, data): """Deserialize value. Concrete :class:`Field` classes should implement this method. :param value: The value to be deserialized. :param str attr: The attribute/key in `data` to be deserialized. :param dict data: The raw input data passed to the `Schema.load`. :raise ValidationError: In case of formatting or validation failure. :return: The deserialized value. .. versionchanged:: 2.0.0 Added ``attr`` and ``data`` parameters. """ return value # Properties @property def context(self): """The context dictionary for the parent :class:`Schema`.""" return self.parent.context @property def root(self): """Reference to the `Schema` that this field belongs to even if it is buried in a `List`. Return `None` for unbound fields. """ ret = self while hasattr(ret, 'parent') and ret.parent: ret = ret.parent return ret if isinstance(ret, SchemaABC) else None class Raw(Field): """Field that applies no formatting or validation.""" pass class Nested(Field): """Allows you to nest a :class:`Schema ` inside a field. Examples: :: user = fields.Nested(UserSchema) user2 = fields.Nested('UserSchema') # Equivalent to above collaborators = fields.Nested(UserSchema, many=True, only='id') parent = fields.Nested('self') 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 Schema nested: The Schema class or class name (string) to nest, or ``"self"`` to nest the :class:`Schema` within itself. :param default: Default value to if attribute is missing or None :param tuple exclude: A list or tuple of fields to exclude. :param required: Raise an :exc:`ValidationError` during deserialization if the field, *and* any required field values specified in the `nested` schema, are not found in the data. If not a `bool` (e.g. a `str`), the provided value will be used as the message of the :exc:`ValidationError` instead of the default message. :param only: A tuple or string of the field(s) to marshal. If `None`, all fields will be marshalled. If a field name (string) is given, only a single value will be returned as output instead of a dictionary. This parameter takes precedence over ``exclude``. :param bool many: Whether the field is a collection of objects. :param kwargs: The same keyword arguments that :class:`Field` receives. """ default_error_messages = { 'type': 'Invalid type.', } def __init__(self, nested, default=missing_, exclude=tuple(), only=None, **kwargs): self.nested = nested self.only = only self.exclude = exclude self.many = kwargs.get('many', False) self.__schema = None # Cached Schema instance self.__updated_fields = False super(Nested, self).__init__(default=default, **kwargs) @property def schema(self): """The nested Schema object. .. versionchanged:: 1.0.0 Renamed from `serializer` to `schema` """ if not self.__schema: # Ensure that only parameter is a tuple if isinstance(self.only, basestring): only = (self.only,) else: only = self.only # Inherit context from parent. context = getattr(self.parent, 'context', {}) if isinstance(self.nested, SchemaABC): self.__schema = self.nested self.__schema.context.update(context) elif isinstance(self.nested, type) and \ issubclass(self.nested, SchemaABC): self.__schema = self.nested(many=self.many, only=only, exclude=self.exclude, context=context, load_only=self._nested_normalized_option('load_only'), dump_only=self._nested_normalized_option('dump_only')) elif isinstance(self.nested, basestring): if self.nested == _RECURSIVE_NESTED: parent_class = self.parent.__class__ self.__schema = parent_class(many=self.many, only=only, exclude=self.exclude, context=context, load_only=self._nested_normalized_option('load_only'), dump_only=self._nested_normalized_option('dump_only')) else: schema_class = class_registry.get_class(self.nested) self.__schema = schema_class(many=self.many, only=only, exclude=self.exclude, context=context, load_only=self._nested_normalized_option('load_only'), dump_only=self._nested_normalized_option('dump_only')) else: raise ValueError('Nested fields must be passed a ' 'Schema, not {0}.'.format(self.nested.__class__)) self.__schema.ordered = getattr(self.parent, 'ordered', False) return self.__schema def _nested_normalized_option(self, option_name): nested_field = '%s.' % 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): # 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 if not self.__updated_fields: schema._update_fields(obj=nested_obj, many=self.many) self.__updated_fields = True ret, errors = schema.dump(nested_obj, many=self.many, update_fields=not self.__updated_fields) if isinstance(self.only, basestring): # self.only is a field name if self.many: return utils.pluck(ret, key=self.only) else: return ret[self.only] if errors: raise ValidationError(errors, data=ret) return ret def _deserialize(self, value, attr, data): if self.many and not utils.is_collection(value): self.fail('type', input=value, type=value.__class__.__name__) if isinstance(self.only, basestring): # self.only is a field name if self.many: value = [{self.only: v} for v in value] else: value = {self.only: value} data, errors = self.schema.load(value) if errors: raise ValidationError(errors, data=data) return data def _validate_missing(self, value): """Validate missing values. Raise a :exc:`ValidationError` if `value` should be considered missing. """ if value is missing_ and hasattr(self, 'required'): if self.nested == _RECURSIVE_NESTED: self.fail('required') errors = self._check_required() if errors: raise ValidationError(errors) else: super(Nested, self)._validate_missing(value) def _check_required(self): errors = {} if self.required: for field_name, field in self.schema.fields.items(): if not field.required: continue error_field_name = field.load_from or field_name if ( isinstance(field, Nested) and self.nested != _RECURSIVE_NESTED and field.nested != _RECURSIVE_NESTED ): errors[error_field_name] = field._check_required() else: try: field._validate_missing(field.missing) except ValidationError as ve: errors[error_field_name] = ve.messages if self.many and errors: errors = {0: errors} # No inner errors; just raise required error like normal if not errors: self.fail('required') return errors class List(Field): """A list field, composed with another `Field` class or instance. Example: :: numbers = fields.List(fields.Float()) :param Field cls_or_instance: A field class or instance. :param bool default: Default value for serialization. :param kwargs: The same keyword arguments that :class:`Field` receives. .. versionchanged:: 2.0.0 The ``allow_none`` parameter now applies to deserialization and has the same semantics as the other fields. """ default_error_messages = { 'invalid': 'Not a valid list.', } def __init__(self, cls_or_instance, **kwargs): super(List, self).__init__(**kwargs) if isinstance(cls_or_instance, type): if not issubclass(cls_or_instance, FieldABC): raise ValueError('The type of the list elements ' 'must be a subclass of ' 'marshmallow.base.FieldABC') self.container = cls_or_instance() else: if not isinstance(cls_or_instance, FieldABC): raise ValueError('The instances of the list ' 'elements must be of type ' 'marshmallow.base.FieldABC') self.container = cls_or_instance def get_value(self, obj, attr, accessor=None): """Return the value for a given key from an object.""" value = super(List, self).get_value(obj, attr, accessor=accessor) if self.container.attribute: if utils.is_collection(value): return [ self.container.get_value(each, self.container.attribute) for each in value ] return self.container.get_value(value, self.container.attribute) return value def _add_to_schema(self, field_name, schema): super(List, self)._add_to_schema(field_name, schema) self.container.parent = self self.container.name = field_name def _serialize(self, value, attr, obj): if value is None: return None if utils.is_collection(value): return [self.container._serialize(each, attr, obj) for each in value] return [self.container._serialize(value, attr, obj)] def _deserialize(self, value, attr, data): if not utils.is_collection(value): self.fail('invalid') result = [] errors = {} for idx, each in enumerate(value): try: result.append(self.container.deserialize(each)) except ValidationError as e: result.append(e.data) errors.update({idx: e.messages}) if errors: raise ValidationError(errors, data=result) return result class String(Field): """A string field. :param kwargs: The same keyword arguments that :class:`Field` receives. """ default_error_messages = { 'invalid': 'Not a valid string.' } def _serialize(self, value, attr, obj): if value is None: return None return utils.ensure_text_type(value) def _deserialize(self, value, attr, data): if not isinstance(value, basestring): self.fail('invalid') return utils.ensure_text_type(value) class UUID(String): """A UUID field.""" default_error_messages = { 'invalid_uuid': 'Not a valid UUID.', 'invalid_guid': 'Not a valid UUID.' # TODO: Remove this in marshmallow 3.0 } def _validated(self, value): """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: return uuid.UUID(value) except (ValueError, AttributeError): self.fail('invalid_uuid') def _serialize(self, value, attr, obj): validated = str(self._validated(value)) if value is not None else None return super(String, self)._serialize(validated, attr, obj) def _deserialize(self, value, attr, data): return self._validated(value) class Number(Field): """Base class for number fields. :param bool as_string: If True, format the serialized value as a string. :param kwargs: The same keyword arguments that :class:`Field` receives. """ num_type = float default_error_messages = { 'invalid': 'Not a valid number.' } def __init__(self, as_string=False, **kwargs): self.as_string = as_string super(Number, self).__init__(**kwargs) def _format_num(self, value): """Return the number value for value, given this field's `num_type`.""" if value is None: return None # (value is True or value is False) is ~5x faster than isinstance(value, bool) if value is True or value is False: raise TypeError( 'value must be a Number, not a boolean. value is ' '{}'.format(value)) return self.num_type(value) def _validated(self, value): """Format the value or raise a :exc:`ValidationError` if an error occurs.""" try: return self._format_num(value) except (TypeError, ValueError) as err: self.fail('invalid') def serialize(self, attr, obj, accessor=None): """Pulls the value for the given key from the object and returns the serialized number representation. Return a string if `self.as_string=True`, othewise return this field's `num_type`. Receives the same `args` and `kwargs` as `Field`. """ ret = Field.serialize(self, attr, obj, accessor=accessor) return self._to_string(ret) if (self.as_string and ret not in (None, missing_)) else ret def _to_string(self, value): return str(value) def _serialize(self, value, attr, obj): return self._validated(value) def _deserialize(self, value, attr, data): return self._validated(value) class Integer(Number): """An integer field. :param kwargs: The same keyword arguments that :class:`Number` receives. """ num_type = int default_error_messages = { 'invalid': 'Not a valid integer.' } # override Number def __init__(self, strict=False, **kwargs): self.strict = strict super(Integer, self).__init__(**kwargs) # override Number def _format_num(self, value): if self.strict and isinstance(value, numbers.Number): if not isinstance(value, numbers.Integral): self.fail('invalid') return super(Integer, self)._format_num(value) class Decimal(Number): """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 int 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 bool allow_nan: If `True`, `NaN`, `Infinity` and `-Infinity` are allowed, even though they are illegal according to the JSON specification. :param bool 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 = { 'special': 'Special numeric values are not permitted.', } def __init__(self, places=None, rounding=None, allow_nan=False, as_string=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(Decimal, self).__init__(as_string=as_string, **kwargs) # override Number def _format_num(self, value): if value is None: return None num = decimal.Decimal(str(value)) if self.allow_nan: if num.is_nan(): return decimal.Decimal('NaN') # avoid sNaN, -sNaN and -NaN else: if num.is_nan() or num.is_infinite(): self.fail('special') 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): try: return super(Decimal, self)._validated(value) except decimal.InvalidOperation: self.fail('invalid') # override Number def _to_string(self, value): return format(value, 'f') class Boolean(Field): """A boolean field. :param set 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 set 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', '1', 1, True } #: Default falsy values. falsy = { 'f', 'F', 'false', 'False', 'FALSE', 'off', 'Off', 'OFF', '0', 0, 0.0, False } default_error_messages = { 'invalid': 'Not a valid boolean.' } def __init__(self, truthy=None, falsy=None, **kwargs): super(Boolean, self).__init__(**kwargs) if truthy is not None: self.truthy = set(truthy) if falsy is not None: self.falsy = set(falsy) def _serialize(self, value, attr, obj): if value is None: return None elif value in self.truthy: return True elif value in self.falsy: return False return bool(value) def _deserialize(self, value, attr, data): if not self.truthy: return bool(value) else: try: if value in self.truthy: return True elif value in self.falsy: return False except TypeError: pass self.fail('invalid') class FormattedString(Field): """Interpolate other values from the object into this field. The syntax for the source string is the same as the string `str.format` method from the python stdlib. :: class UserSchema(Schema): name = fields.String() greeting = fields.FormattedString('Hello {name}') ser = UserSchema() res = ser.dump(user) res.data # => {'name': 'Monty', 'greeting': 'Hello Monty'} """ default_error_messages = { 'format': 'Cannot format string with given data.' } _CHECK_ATTRIBUTE = False def __init__(self, src_str, *args, **kwargs): Field.__init__(self, *args, **kwargs) self.src_str = text_type(src_str) def _serialize(self, value, attr, obj): try: data = utils.to_marshallable_type(obj) return self.src_str.format(**data) except (TypeError, IndexError) as error: self.fail('format') class Float(Number): """ A double as IEEE-754 double precision string. :param bool as_string: If True, format the value as a string. :param kwargs: The same keyword arguments that :class:`Number` receives. """ num_type = float class DateTime(Field): """A formatted datetime string in UTC. Example: ``'2014-12-22T03:12:58.019077+00:00'`` Timezone-naive `datetime` objects are converted to UTC (+00:00) by :meth:`Schema.dump `. :meth:`Schema.load ` returns `datetime` objects that are timezone-aware. :param str format: Either ``"rfc"`` (for RFC822), ``"iso"`` (for ISO8601), or a date format string. If `None`, defaults to "iso". :param kwargs: The same keyword arguments that :class:`Field` receives. """ DATEFORMAT_SERIALIZATION_FUNCS = { 'iso': utils.isoformat, 'iso8601': utils.isoformat, 'rfc': utils.rfcformat, 'rfc822': utils.rfcformat, } DATEFORMAT_DESERIALIZATION_FUNCS = { 'iso': utils.from_iso, 'iso8601': utils.from_iso, 'rfc': utils.from_rfc, 'rfc822': utils.from_rfc, } DEFAULT_FORMAT = 'iso' localtime = False default_error_messages = { 'invalid': 'Not a valid datetime.', 'format': '"{input}" cannot be formatted as a datetime.', } def __init__(self, format=None, **kwargs): super(DateTime, self).__init__(**kwargs) # Allow this to be None. It may be set later in the ``_serialize`` # or ``_desrialize`` methods This allows a Schema to dynamically set the # dateformat, e.g. from a Meta option self.dateformat = format def _add_to_schema(self, field_name, schema): super(DateTime, self)._add_to_schema(field_name, schema) self.dateformat = self.dateformat or schema.opts.dateformat def _serialize(self, value, attr, obj): if value is None: return None self.dateformat = self.dateformat or self.DEFAULT_FORMAT format_func = self.DATEFORMAT_SERIALIZATION_FUNCS.get(self.dateformat, None) if format_func: try: return format_func(value, localtime=self.localtime) except (AttributeError, ValueError) as err: self.fail('format', input=value) else: return value.strftime(self.dateformat) def _deserialize(self, value, attr, data): if not value: # Falsy values, e.g. '', None, [] are not valid raise self.fail('invalid') self.dateformat = self.dateformat or self.DEFAULT_FORMAT func = self.DATEFORMAT_DESERIALIZATION_FUNCS.get(self.dateformat) if func: try: return func(value) except (TypeError, AttributeError, ValueError): raise self.fail('invalid') elif self.dateformat: try: return dt.datetime.strptime(value, self.dateformat) except (TypeError, AttributeError, ValueError): raise self.fail('invalid') elif utils.dateutil_available: try: return utils.from_datestring(value) except TypeError: raise self.fail('invalid') else: warnings.warn('It is recommended that you install python-dateutil ' 'for improved datetime deserialization.') raise self.fail('invalid') class LocalDateTime(DateTime): """A formatted datetime string in localized time, relative to UTC. ex. ``"Sun, 10 Nov 2013 08:23:45 -0600"`` Takes the same arguments as :class:`DateTime `. """ localtime = True class Time(Field): """ISO8601-formatted time string. :param kwargs: The same keyword arguments that :class:`Field` receives. """ default_error_messages = { 'invalid': 'Not a valid time.', 'format': '"{input}" cannot be formatted as a time.', } def _serialize(self, value, attr, obj): if value is None: return None try: ret = value.isoformat() except AttributeError: self.fail('format', input=value) if value.microsecond: return ret[:15] return ret def _deserialize(self, value, attr, data): """Deserialize an ISO8601-formatted time to a :class:`datetime.time` object.""" if not value: # falsy values are invalid self.fail('invalid') raise err try: return utils.from_iso_time(value) except (AttributeError, TypeError, ValueError): self.fail('invalid') class Date(Field): """ISO8601-formatted date string. :param kwargs: The same keyword arguments that :class:`Field` receives. """ default_error_messages = { 'invalid': 'Not a valid date.', 'format': '"{input}" cannot be formatted as a date.', } def _serialize(self, value, attr, obj): if value is None: return None try: return value.isoformat() except AttributeError: self.fail('format', input=value) return value def _deserialize(self, value, attr, data): """Deserialize an ISO8601-formatted date string to a :class:`datetime.date` object. """ if not value: # falsy values are invalid self.fail('invalid') try: return utils.from_iso_date(value) except (AttributeError, TypeError, ValueError): self.fail('invalid') class TimeDelta(Field): """A field that (de)serializes a :class:`datetime.timedelta` object to an integer and vice versa. The integer can represent the number of days, seconds or microseconds. :param str precision: Influences how the integer is interpreted during (de)serialization. Must be 'days', 'seconds' or 'microseconds'. :param str error: Error message stored upon validation failure. :param kwargs: The same keyword arguments that :class:`Field` receives. .. versionchanged:: 2.0.0 Always serializes to an integer value to avoid rounding errors. Add `precision` parameter. """ DAYS = 'days' SECONDS = 'seconds' MICROSECONDS = 'microseconds' default_error_messages = { 'invalid': 'Not a valid period of time.', 'format': '{input!r} cannot be formatted as a timedelta.' } def __init__(self, precision='seconds', error=None, **kwargs): precision = precision.lower() units = (self.DAYS, self.SECONDS, self.MICROSECONDS) if precision not in units: msg = 'The precision must be "{0}", "{1}" or "{2}".'.format(*units) raise ValueError(msg) self.precision = precision super(TimeDelta, self).__init__(error=error, **kwargs) def _serialize(self, value, attr, obj): if value is None: return None try: days = value.days if self.precision == self.DAYS: return days else: seconds = days * 86400 + value.seconds if self.precision == self.SECONDS: return seconds else: # microseconds return seconds * 10**6 + value.microseconds # flake8: noqa except AttributeError: self.fail('format', input=value) def _deserialize(self, value, attr, data): try: value = int(value) except (TypeError, ValueError): self.fail('invalid') kwargs = {self.precision: value} try: return dt.timedelta(**kwargs) except OverflowError: self.fail('invalid') class Dict(Field): """A dict field. Supports dicts and dict-like objects. .. note:: This field is only appropriate when the structure of nested data is not known. For structured data, use `Nested`. .. versionadded:: 2.1.0 """ default_error_messages = { 'invalid': 'Not a valid mapping type.' } def _deserialize(self, value, attr, data): if isinstance(value, collections.Mapping): return value else: self.fail('invalid') class ValidatedField(Field): """A field that validates input on serialization.""" def _validated(self, value): raise NotImplementedError('Must implement _validate method') def _serialize(self, value, *args, **kwargs): ret = super(ValidatedField, self)._serialize(value, *args, **kwargs) return self._validated(ret) class Url(ValidatedField, String): """A validated URL field. Validation occurs during both serialization and deserialization. :param default: Default value for the field if the attribute is not set. :param str attribute: The name of the attribute to get the value from. If `None`, assumes the attribute has the same name as the field. :param bool relative: Allow relative URLs. :param kwargs: The same keyword arguments that :class:`String` receives. """ default_error_messages = {'invalid': 'Not a valid URL.'} def __init__(self, relative=False, schemes=None, **kwargs): String.__init__(self, **kwargs) self.relative = relative # Insert validation into self.validators so that multiple errors can be # stored. self.validators.insert(0, validate.URL( relative=self.relative, schemes=schemes, error=self.error_messages['invalid'] )) def _validated(self, value): if value is None: return None return validate.URL( relative=self.relative, error=self.error_messages['invalid'] )(value) class Email(ValidatedField, String): """A validated email field. Validation occurs during both serialization and deserialization. :param args: The same positional arguments that :class:`String` receives. :param kwargs: The same keyword arguments that :class:`String` receives. """ default_error_messages = {'invalid': 'Not a valid email address.'} def __init__(self, *args, **kwargs): String.__init__(self, *args, **kwargs) # Insert validation into self.validators so that multiple errors can be # stored. self.validators.insert(0, validate.Email(error=self.error_messages['invalid'])) def _validated(self, value): if value is None: return None return validate.Email( error=self.error_messages['invalid'] )(value) class Method(Field): """A field that takes the value returned by a `Schema` method. :param str 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 str 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.0.0 Removed optional ``context`` parameter on methods. Use ``self.context`` instead. .. 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=None, deserialize=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(Method, self).__init__(**kwargs) self.serialize_method_name = serialize self.deserialize_method_name = deserialize def _serialize(self, value, attr, obj): if not self.serialize_method_name: return missing_ method = utils.callable_or_raise( getattr(self.parent, self.serialize_method_name, None) ) return method(obj) def _deserialize(self, value, attr, data): if self.deserialize_method_name: method = utils.callable_or_raise( getattr(self.parent, self.deserialize_method_name, None) ) return method(value) return value class Function(Field): """A field that takes the value returned by a function. :param callable 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 callable 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.0 Removed ``func`` parameter. """ _CHECK_ATTRIBUTE = False def __init__(self, serialize=None, deserialize=None, func=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(Function, self).__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): return self._call_or_raise(self.serialize_func, obj, attr) def _deserialize(self, value, attr, data): 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 = 'No context available for Function field {0!r}'.format(attr) raise ValidationError(msg) return func(value, self.parent.context) else: 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. .. versionadded:: 2.0.0 """ _CHECK_ATTRIBUTE = False def __init__(self, constant, **kwargs): super(Constant, self).__init__(**kwargs) self.constant = constant self.missing = constant self.default = constant def _serialize(self, value, *args, **kwargs): return self.constant def _deserialize(self, value, *args, **kwargs): return self.constant # Aliases URL = Url Str = String Bool = Boolean Int = Integer marshmallow-3.0.0b3/marshmallow/marshalling.py000066400000000000000000000306321314633611600214760ustar00rootroot00000000000000# -*- coding: utf-8 -*- """Utility classes and values used for marshalling and unmarshalling objects to and from primitive types. .. warning:: This module is treated as private API. Users should not need to use this module directly. """ from __future__ import unicode_literals from marshmallow.utils import is_collection, missing, set_value from marshmallow.compat import text_type, iteritems from marshmallow.exceptions import ( ValidationError, ) __all__ = [ 'Marshaller', 'Unmarshaller', ] # Key used for field-level validation errors on nested fields FIELD = '_field' class ErrorStore(object): def __init__(self): #: Dictionary of errors stored during serialization self.errors = {} #: List of `Field` objects which have validation errors self.error_fields = [] #: List of field_names which have validation errors self.error_field_names = [] #: True while (de)serializing a collection self._pending = False #: Dictionary of extra kwargs from user raised exception self.error_kwargs = {} def reset_errors(self): self.errors = {} self.error_field_names = [] self.error_fields = [] self.error_kwargs = {} def get_errors(self, index=None): if index is not None: errors = self.errors.get(index, {}) self.errors[index] = errors else: errors = self.errors return errors def call_and_store(self, getter_func, data, field_name, field_obj, index=None): """Call ``getter_func`` with ``data`` as its argument, and store any `ValidationErrors`. :param callable getter_func: Function for getting the serialized/deserialized value from ``data``. :param data: The data passed to ``getter_func``. :param str field_name: Field name. :param FieldABC field_obj: Field object that performs the serialization/deserialization behavior. :param int index: Index of the item being validated, if validating a collection, otherwise `None`. """ try: value = getter_func(data) except ValidationError as err: # Store validation errors self.error_kwargs.update(err.kwargs) self.error_fields.append(field_obj) self.error_field_names.append(field_name) errors = self.get_errors(index=index) # Warning: Mutation! if isinstance(err.messages, dict): errors[field_name] = err.messages elif isinstance(errors.get(field_name), dict): errors[field_name].setdefault(FIELD, []).extend(err.messages) else: errors.setdefault(field_name, []).extend(err.messages) # When a Nested field fails validation, the marshalled data is stored # on the ValidationError's data attribute value = err.data or missing return value class Marshaller(ErrorStore): """Callable class responsible for serializing data and storing errors. :param str prefix: Optional prefix that will be prepended to all the serialized field names. """ def __init__(self, prefix=''): self.prefix = prefix ErrorStore.__init__(self) def serialize(self, obj, fields_dict, many=False, accessor=None, dict_class=dict, index_errors=True, index=None): """Takes raw data (a dict, list, or other object) and a dict of fields to output and serializes the data based on those fields. :param obj: The actual object(s) from which the fields are taken from :param dict fields_dict: Mapping of field names to :class:`Field` objects. :param bool many: Set to `True` if ``data`` should be serialized as a collection. :param callable accessor: Function to use for getting values from ``obj``. :param type dict_class: Dictionary class used to construct the output. :param bool index_errors: Whether to store the index of invalid items in ``self.errors`` when ``many=True``. :param int index: Index of the item being serialized (for storing errors) if serializing a collection, otherwise `None`. :return: A dictionary of the marshalled data .. versionchanged:: 1.0.0 Renamed from ``marshal``. """ # Reset errors dict if not serializing a collection if not self._pending: self.reset_errors() if many and obj is not None: self._pending = True ret = [self.serialize(d, fields_dict, many=False, dict_class=dict_class, accessor=accessor, index=idx, index_errors=index_errors) for idx, d in enumerate(obj)] self._pending = False if self.errors: raise ValidationError( self.errors, field_names=self.error_field_names, fields=self.error_fields, data=ret, ) return ret items = [] for attr_name, field_obj in iteritems(fields_dict): if getattr(field_obj, 'load_only', False): continue key = ''.join([self.prefix or '', field_obj.dump_to or attr_name]) getter = lambda d: field_obj.serialize(attr_name, d, accessor=accessor) value = self.call_and_store( getter_func=getter, data=obj, field_name=key, field_obj=field_obj, index=(index if index_errors else None) ) if value is missing: continue items.append((key, value)) ret = dict_class(items) if self.errors and not self._pending: raise ValidationError( self.errors, field_names=self.error_field_names, fields=self.error_fields, data=ret ) return ret # Make an instance callable __call__ = serialize # Key used for schema-level validation errors SCHEMA = '_schema' class Unmarshaller(ErrorStore): """Callable class responsible for deserializing data and storing errors. .. versionadded:: 1.0.0 """ default_schema_validation_error = 'Invalid data.' def run_validator(self, validator_func, output, original_data, fields_dict, index=None, many=False, pass_original=False): try: if pass_original: # Pass original, raw data (before unmarshalling) res = validator_func(output, original_data) else: res = validator_func(output) if res is False: raise ValidationError(self.default_schema_validation_error) except ValidationError as err: errors = self.get_errors(index=index) self.error_kwargs.update(err.kwargs) # Store or reraise errors if err.field_names: field_names = err.field_names field_objs = [fields_dict[each] if each in fields_dict else None for each in field_names] else: field_names = [SCHEMA] field_objs = [] self.error_field_names = field_names self.error_fields = field_objs for field_name in field_names: if isinstance(err.messages, (list, tuple)): # self.errors[field_name] may be a dict if schemas are nested if isinstance(errors.get(field_name), dict): errors[field_name].setdefault( SCHEMA, [] ).extend(err.messages) else: errors.setdefault(field_name, []).extend(err.messages) elif isinstance(err.messages, dict): errors.setdefault(field_name, []).append(err.messages) else: errors.setdefault(field_name, []).append(text_type(err)) def deserialize(self, data, fields_dict, many=False, partial=False, dict_class=dict, index_errors=True, index=None): """Deserialize ``data`` based on the schema defined by ``fields_dict``. :param dict data: The data to deserialize. :param dict fields_dict: Mapping of field names to :class:`Field` objects. :param bool many: Set to `True` if ``data`` should be deserialized as a collection. :param bool|tuple partial: Whether to ignore missing fields. If its value is an iterable, only missing fields listed in that iterable will be ignored. :param type dict_class: Dictionary class used to construct the output. :param bool index_errors: Whether to store the index of invalid items in ``self.errors`` when ``many=True``. :param int index: Index of the item being serialized (for storing errors) if serializing a collection, otherwise `None`. :return: A dictionary of the deserialized data. """ # Reset errors if not deserializing a collection if not self._pending: self.reset_errors() if many and data is not None: self._pending = True ret = [self.deserialize(d, fields_dict, many=False, partial=partial, dict_class=dict_class, index=idx, index_errors=index_errors) for idx, d in enumerate(data)] self._pending = False if self.errors: raise ValidationError( self.errors, field_names=self.error_field_names, fields=self.error_fields, data=ret, ) return ret if data is not None: partial_is_collection = is_collection(partial) ret = dict_class() for attr_name, field_obj in iteritems(fields_dict): if field_obj.dump_only: continue try: raw_value = data.get(attr_name, missing) except AttributeError: # Input data is not a dict errors = self.get_errors(index=index) msg = field_obj.error_messages['type'].format( input=data, input_type=data.__class__.__name__ ) self.error_field_names = [SCHEMA] self.error_fields = [] errors = self.get_errors() errors.setdefault(SCHEMA, []).append(msg) # Input data type is incorrect, so we can bail out early break field_name = attr_name if raw_value is missing and field_obj.load_from: field_name = field_obj.load_from raw_value = data.get(field_obj.load_from, 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 _miss = field_obj.missing raw_value = _miss() if callable(_miss) else _miss if raw_value is missing and not field_obj.required: continue getter = lambda val: field_obj.deserialize( val, field_obj.load_from or attr_name, data ) value = self.call_and_store( getter_func=getter, data=raw_value, field_name=field_name, field_obj=field_obj, index=(index if index_errors else None) ) if value is not missing: key = fields_dict[attr_name].attribute or attr_name set_value(ret, key, value) else: ret = None if self.errors and not self._pending: raise ValidationError( self.errors, field_names=self.error_field_names, fields=self.error_fields, data=ret, ) return ret # Make an instance callable __call__ = deserialize marshmallow-3.0.0b3/marshmallow/orderedset.py000066400000000000000000000056331314633611600213400ustar00rootroot00000000000000# -*- coding: utf-8 -*- # 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. import collections class OrderedSet(collections.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) 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 '%s()' % (self.__class__.__name__,) return '%s(%r)' % (self.__class__.__name__, list(self)) 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.0.0b3/marshmallow/schema.py000066400000000000000000001117471314633611600204440ustar00rootroot00000000000000# -*- coding: utf-8 -*- """The :class:`Schema` class, including its metaclass and options (class Meta).""" from __future__ import absolute_import, unicode_literals from collections import defaultdict, Mapping import copy import datetime as dt import decimal import inspect import json import uuid import warnings from collections import namedtuple, OrderedDict import functools from marshmallow import base, fields, utils, class_registry, marshalling from marshmallow.compat import with_metaclass, iteritems, text_type, binary_type from marshmallow.exceptions import ValidationError from marshmallow.orderedset import OrderedSet from marshmallow.decorators import (PRE_DUMP, POST_DUMP, PRE_LOAD, POST_LOAD, VALIDATES, VALIDATES_SCHEMA) from marshmallow.utils import missing #: Return type of :meth:`Schema.dump` including serialized data and errors MarshalResult = namedtuple('MarshalResult', ['data', 'errors']) #: Return type of :meth:`Schema.load`, including deserialized data and errors UnmarshalResult = namedtuple('UnmarshalResult', ['data', 'errors']) def _get_fields(attrs, field_class, pop=False, ordered=False): """Get fields from a class. If ordered=True, fields will sorted by creation index. :param attrs: Mapping of class attributes :param type field_class: Base field class :param bool pop: Remove matching fields """ fields = [ (field_name, field_value) for field_name, field_value in iteritems(attrs) if utils.is_instance_or_subclass(field_value, field_class) ] if pop: for field_name, _ in fields: del attrs[field_name] if ordered: fields.sort(key=lambda pair: pair[1]._creation_index) return fields # This function allows Schemas to inherit from non-Schema classes and ensures # inheritance according to the MRO def _get_fields_by_mro(klass, field_class, ordered=False): """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 type klass: Class whose fields to retrieve :param type field_class: Base field class """ mro = inspect.getmro(klass) # Loop over mro in reverse to maintain correct order of fields return sum( ( _get_fields( getattr(base, '_declared_fields', base.__dict__), field_class, ordered=ordered ) for base in mro[:0:-1] ), [], ) class SchemaMeta(type): """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. """ def __new__(mcs, name, bases, attrs): 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, base.FieldABC, pop=True, ordered=ordered) klass = super(SchemaMeta, mcs).__new__(mcs, name, bases, attrs) inherited_fields = _get_fields_by_mro(klass, base.FieldABC, ordered=ordered) # Use getattr rather than attrs['Meta'] so that we get inheritance for free meta = getattr(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 specifid in the `include` class Meta option cls_fields += list(klass.opts.include.items()) dict_cls = OrderedDict if ordered else dict # Assign _declared_fields on class klass._declared_fields = mcs.get_declared_fields( klass=klass, cls_fields=cls_fields, inherited_fields=inherited_fields, dict_cls=dict_cls ) return klass @classmethod def get_declared_fields(mcs, klass, cls_fields, inherited_fields, dict_cls): """Returns a dictionary of field_name => `Field` pairs declard on the class. This is exposed mainly so that plugins can add additional fields, e.g. fields computed from class Meta options. :param type klass: The class object. :param dict cls_fields: The fields declared on the class, including those added by the ``include`` class Meta option. :param dict inherited_fileds: Inherited fields. :param type dict_class: Either `dict` or `OrderedDict`, depending on the whether the user specified `ordered=True`. """ return dict_cls(inherited_fields + cls_fields) # NOTE: self is the class object def __init__(self, name, bases, attrs): super(SchemaMeta, self).__init__(name, bases, attrs) if name: class_registry.register(name, self) self._resolve_processors() def _resolve_processors(self): """Add in the decorated processors By doing this after constructing the class, we let standard inheritance do all the hard work. """ mro = inspect.getmro(self) self._has_processors = False self.__processors__ = defaultdict(list) for attr_name in dir(self): # 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: processor_tags = attr.__marshmallow_tags__ except AttributeError: continue self._has_processors = bool(processor_tags) for tag in processor_tags: # Use name here so we can get the bound method later, in case # the processor was a descriptor or something. self.__processors__[tag].append(attr_name) class SchemaOpts(object): """class Meta options for the :class:`Schema`. Defines defaults.""" def __init__(self, meta, ordered=False): 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.strict = getattr(meta, 'strict', False) self.dateformat = getattr(meta, 'dateformat', None) if hasattr(meta, 'json_module'): warnings.warn( 'The json_module class Meta option is deprecated. Use render_module instead.', DeprecationWarning ) render_module = getattr(meta, 'json_module', json) else: render_module = json self.render_module = getattr(meta, 'render_module', render_module) 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', ()) class BaseSchema(base.SchemaABC): """Base schema class with which to define custom schemas. Example usage: .. code-block:: python import datetime as dt from marshmallow import Schema, fields class Album(object): def __init__(self, title, release_date): self.title = title self.release_date = release_date class AlbumSchema(Schema): title = fields.Str() release_date = fields.Date() # Or, equivalently class AlbumSchema2(Schema): class Meta: fields = ("title", "release_date") album = Album("Beggars Banquet", dt.date(1968, 12, 6)) schema = AlbumSchema() data, errors = schema.dump(album) data # {'release_date': '1968-12-06', 'title': 'Beggars Banquet'} :param tuple only: A list or tuple of fields to serialize. If `None`, all fields will be serialized. Nested fields can be represented with dot delimiters. :param tuple exclude: A list or tuple of fields to exclude from the serialized result. Nested fields can be represented with dot delimiters. :param str prefix: Optional prefix that will be prepended to all the serialized field names. :param bool strict: If `True`, raise errors if invalid data are passed in instead of failing silently and storing the errors. :param bool many: Should be set to `True` if ``obj`` is a collection so that the object will be serialized to a list. :param dict context: Optional context passed to :class:`fields.Method` and :class:`fields.Function` fields. :param tuple load_only: A list or tuple of fields to skip during serialization :param tuple dump_only: A list or tuple of fields to skip during deserialization, read-only fields :param bool|tuple partial: Whether to ignore missing fields. If its value is an iterable, only missing fields listed in that iterable will be ignored. .. versionchanged:: 2.0.0 `__validators__`, `__preprocessors__`, and `__data_handlers__` are removed in favor of `marshmallow.decorators.validates_schema`, `marshmallow.decorators.pre_load` and `marshmallow.decorators.post_dump`. `__accessor__` and `__error_handler__` are deprecated. Implement the `handle_error` and `get_attribute` methods instead. """ TYPE_MAPPING = { text_type: fields.String, binary_type: fields.String, dt.datetime: fields.DateTime, float: fields.Float, bool: fields.Boolean, tuple: fields.Raw, list: fields.Raw, set: fields.Raw, int: fields.Integer, uuid.UUID: fields.UUID, dt.time: fields.Time, dt.date: fields.Date, dt.timedelta: fields.TimeDelta, decimal.Decimal: fields.Decimal, } OPTIONS_CLASS = SchemaOpts class Meta(object): """Options object for a Schema. Example usage: :: class Meta: fields = ("id", "email", "date_created") exclude = ("password", "secret_attribute") Available options: - ``fields``: Tuple or list of fields to include in the serialized result. - ``additional``: Tuple or list of fields to include *in addition* to the explicitly declared fields. ``additional`` and ``fields`` are mutually-exclusive options. - ``include``: 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. May be an `OrderedDict`. - ``exclude``: Tuple or list of fields to exclude in the serialized result. Nested fields can be represented with dot delimiters. - ``dateformat``: Date format for all DateTime fields that do not have their date format explicitly specified. - ``strict``: If `True`, raise errors during marshalling rather than storing them. - ``render_module``: Module to use for `loads` and `dumps`. Defaults to `json` from the standard library. Defaults to the ``json`` module in the stdlib. - ``ordered``: If `True`, order serialization output according to the order in which fields were declared. Output of `Schema.dump` will be a `collections.OrderedDict`. - ``index_errors``: If `True`, errors dictionaries will include the index of invalid items in a collection. - ``load_only``: Tuple or list of fields to exclude from serialized results. - ``dump_only``: Tuple or list of fields to exclude from deserialization """ pass def __init__(self, only=(), exclude=(), prefix='', strict=None, many=False, context=None, load_only=(), dump_only=(), partial=False): # copy declared fields from metaclass self.declared_fields = copy.deepcopy(self._declared_fields) self.many = many self.only = only self.exclude = exclude self.prefix = prefix self.strict = strict if strict is not None else self.opts.strict 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 #: Dictionary mapping field_names -> :class:`Field` objects self.fields = self.dict_class() #: Callable marshalling object self._marshal = marshalling.Marshaller( prefix=self.prefix ) #: Callable unmarshalling object self._unmarshal = marshalling.Unmarshaller() self.context = context or {} self._normalize_nested_options() self._types_seen = set() self._update_fields(many=many) def __repr__(self): return '<{ClassName}(many={self.many}, strict={self.strict})>'.format( ClassName=self.__class__.__name__, self=self ) @property def dict_class(self): return OrderedDict if self.ordered else dict @property def set_class(self): return OrderedSet if self.ordered else set ##### Override-able methods ##### def handle_error(self, error, data): """Custom error handler function for the schema. :param ValidationError error: The `ValidationError` raised during (de)serialization. :param data: The original input data. .. versionadded:: 2.0.0 """ pass def get_attribute(self, obj, attr, default): """Defines how to pull values from an object to serialize. .. versionadded:: 2.0.0 .. versionchanged:: 3.0.0a1 Changed position of ``obj`` and ``attr``. """ return utils.get_value(obj, attr, default) ##### Serialization/Deserialization API ##### def dump(self, obj, many=None, update_fields=True, **kwargs): """Serialize an object to native Python data types according to this Schema's fields. :param obj: The object to serialize. :param bool many: Whether to serialize `obj` as a collection. If `None`, the value for `self.many` is used. :param bool update_fields: Whether to update the schema's field classes. Typically set to `True`, but may be `False` when serializing a homogenous collection. This parameter is used by `fields.Nested` to avoid multiple updates. :return: A tuple of the form (``data``, ``errors``) :rtype: `MarshalResult`, a `collections.namedtuple` .. versionadded:: 1.0.0 """ errors = {} many = self.many if many is None else bool(many) if many and utils.is_iterable_but_not_string(obj): obj = list(obj) if self._has_processors: try: processed_obj = self._invoke_dump_processors( PRE_DUMP, obj, many, original_data=obj) except ValidationError as error: errors = error.normalized_messages() result = None else: processed_obj = obj if not errors: if update_fields: obj_type = type(processed_obj) if obj_type not in self._types_seen: self._update_fields(processed_obj, many=many) if not isinstance(processed_obj, Mapping): self._types_seen.add(obj_type) try: result = self._marshal( processed_obj, self.fields, many=many, accessor=self.get_attribute, dict_class=self.dict_class, index_errors=self.opts.index_errors, **kwargs ) except ValidationError as error: errors = self._marshal.errors result = error.data if not errors and self._has_processors: try: result = self._invoke_dump_processors( POST_DUMP, result, many, original_data=obj) except ValidationError as error: errors = error.normalized_messages() if errors: exc = ValidationError( errors, field_names=self._marshal.error_field_names, fields=self._marshal.error_fields, data=obj, valid_data=result, **self._marshal.error_kwargs ) # User-defined error handler self.handle_error(exc, obj) if self.strict: raise exc return MarshalResult(result, errors) def dumps(self, obj, many=None, update_fields=True, *args, **kwargs): """Same as :meth:`dump`, except return a JSON-encoded string. :param obj: The object to serialize. :param bool many: Whether to serialize `obj` as a collection. If `None`, the value for `self.many` is used. :param bool update_fields: Whether to update the schema's field classes. Typically set to `True`, but may be `False` when serializing a homogenous collection. This parameter is used by `fields.Nested` to avoid multiple updates. :return: A tuple of the form (``data``, ``errors``) :rtype: `MarshalResult`, a `collections.namedtuple` .. versionadded:: 1.0.0 """ serialized, errors = self.dump(obj, many=many, update_fields=update_fields) ret = self.opts.render_module.dumps(serialized, *args, **kwargs) return MarshalResult(ret, errors) def load(self, data, many=None, partial=None): """Deserialize a data structure to an object defined by this Schema's fields and :meth:`make_object`. :param dict data: The data to deserialize. :param bool many: Whether to deserialize `data` as a collection. If `None`, the value for `self.many` is used. :param bool|tuple partial: Whether to ignore missing fields. If `None`, the value for `self.partial` is used. If its value is an iterable, only missing fields listed in that iterable will be ignored. :return: A tuple of the form (``data``, ``errors``) :rtype: `UnmarshalResult`, a `collections.namedtuple` .. versionadded:: 1.0.0 """ result, errors = self._do_load(data, many, partial=partial, postprocess=True) return UnmarshalResult(data=result, errors=errors) def loads(self, json_data, many=None, *args, **kwargs): """Same as :meth:`load`, except it takes a JSON string as input. :param str json_data: A JSON string of the data to deserialize. :param bool many: Whether to deserialize `obj` as a collection. If `None`, the value for `self.many` is used. :param bool|tuple partial: Whether to ignore missing fields. If `None`, the value for `self.partial` is used. If its value is an iterable, only missing fields listed in that iterable will be ignored. :return: A tuple of the form (``data``, ``errors``) :rtype: `UnmarshalResult`, a `collections.namedtuple` .. versionadded:: 1.0.0 """ # TODO: This avoids breaking backward compatibility if people were # passing in positional args after `many` for use by `json.loads`, but # ideally we shouldn't have to do this. partial = kwargs.pop('partial', None) data = self.opts.render_module.loads(json_data, *args, **kwargs) return self.load(data, many=many, partial=partial) def validate(self, data, many=None, partial=None): """Validate `data` against the schema, returning a dictionary of validation errors. :param dict data: The data to validate. :param bool many: Whether to validate `data` as a collection. If `None`, the value for `self.many` is used. :param bool|tuple partial: Whether to ignore missing fields. If `None`, the value for `self.partial` is used. If its value is an iterable, only missing fields listed in that iterable will be ignored. :return: A dictionary of validation errors. :rtype: dict .. versionadded:: 1.1.0 """ _, errors = self._do_load(data, many, partial=partial, postprocess=False) return errors ##### Private Helpers ##### def _do_load(self, data, many=None, partial=None, postprocess=True): """Deserialize `data`, returning the deserialized result and a dictonary of validation errors. :param data: The data to deserialize. :param bool many: Whether to deserialize `data` as a collection. If `None`, the value for `self.many` is used. :param bool|tuple 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 bool postprocess: Whether to run post_load methods.. :return: A tuple of the form (`data`, `errors`) """ errors = {} many = self.many if many is None else bool(many) if partial is None: partial = self.partial try: processed_data = self._invoke_load_processors( PRE_LOAD, data, many, original_data=data) except ValidationError as err: errors = err.normalized_messages() result = None if not errors: try: result = self._unmarshal( processed_data, self.fields, many=many, partial=partial, dict_class=self.dict_class, index_errors=self.opts.index_errors, ) except ValidationError as error: result = error.data self._invoke_field_validators(data=result, many=many) errors = self._unmarshal.errors field_errors = bool(errors) # Run schema-level migration try: self._invoke_validators(pass_many=True, data=result, original_data=data, many=many, field_errors=field_errors) except ValidationError as err: errors.update(err.messages) try: self._invoke_validators(pass_many=False, data=result, original_data=data, many=many, field_errors=field_errors) except ValidationError as err: errors.update(err.messages) # Run post processors if not errors and postprocess: try: result = self._invoke_load_processors( POST_LOAD, result, many, original_data=data) except ValidationError as err: errors = err.normalized_messages() if errors: exc = ValidationError( errors, field_names=self._unmarshal.error_field_names, fields=self._unmarshal.error_fields, data=data, valid_data=result, **self._unmarshal.error_kwargs ) self.handle_error(exc, data) if self.strict: raise exc return result, errors def _normalize_nested_options(self): """Apply then flatten nested schema options""" if self.only: # Apply the only option to nested fields. self.__apply_nested_option('only', self.only) # Remove the child field names from the only option. self.only = self.set_class( [field.split('.', 1)[0] for field in self.only]) excludes = set(self.opts.exclude) | set(self.exclude) if excludes: # Apply the exclude option to nested fields. self.__apply_nested_option('exclude', excludes) if self.exclude: # Remove the parent field names from the exclude option. self.exclude = self.set_class( [field for field in self.exclude if '.' not in field]) if self.opts.exclude: # Remove the parent field names from the meta exclude option. self.opts.exclude = self.set_class( [field for field in self.opts.exclude if '.' not in field]) def __apply_nested_option(self, option_name, field_names): """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) 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()): setattr(self.declared_fields[key], option_name, self.set_class(options)) def _update_fields(self, obj=None, many=False): """Update fields based on the passed in object.""" if self.only: # Return only fields specified in only option if self.opts.fields: field_names = self.set_class(self.opts.fields) & self.set_class(self.only) else: field_names = self.set_class(self.only) elif self.opts.fields: # Return fields specified in fields option field_names = self.set_class(self.opts.fields) elif self.opts.additional: # Return declared fields + additional fields field_names = (self.set_class(self.declared_fields.keys()) | self.set_class(self.opts.additional)) else: field_names = self.set_class(self.declared_fields.keys()) # If "exclude" option or param is specified, remove those fields excludes = set(self.opts.exclude) | set(self.exclude) if excludes: field_names = field_names - excludes ret = self.__filter_fields(field_names, obj, many=many) # Set parents self.__set_field_attrs(ret) self.fields = ret return self.fields def on_bind_field(self, field_name, field_obj): """Hook to modify a field when it is bound to the `Schema`. No-op by default.""" return None def __set_field_attrs(self, fields_dict): """Bind fields to the schema, setting any necessary attributes on the fields (e.g. parent and name). Also set field load_only and dump_only values if field_name was specified in ``class Meta``. """ for field_name, field_obj in iteritems(fields_dict): try: if field_name in self.load_only: field_obj.load_only = True if field_name in self.dump_only: field_obj.dump_only = True field_obj._add_to_schema(field_name, self) self.on_bind_field(field_name, field_obj) except TypeError: # field declared as a class, not an instance if (isinstance(field_obj, type) and issubclass(field_obj, base.FieldABC)): msg = ('Field for "{0}" must be declared as a ' 'Field instance, not a class. ' 'Did you mean "fields.{1}()"?' .format(field_name, field_obj.__name__)) raise TypeError(msg) return fields_dict def __filter_fields(self, field_names, obj, many=False): """Return only those field_name:field_obj pairs specified by ``field_names``. :param set field_names: Field names to include in the final return dictionary. :returns: An dict of field_name:field_obj pairs. """ if obj and many: try: # Homogeneous collection # Prefer getitem over iter to prevent breaking serialization # of objects for which iter will modify position in the collection # e.g. Pymongo cursors if hasattr(obj, '__getitem__') and callable(getattr(obj, '__getitem__')): try: obj_prototype = obj[0] except KeyError: obj_prototype = next(iter(obj)) else: obj_prototype = next(iter(obj)) except (StopIteration, IndexError): # Nothing to serialize return {k: v for k, v in self.declared_fields.items() if k in field_names} obj = obj_prototype ret = self.dict_class() for key in field_names: if key in self.declared_fields: ret[key] = self.declared_fields[key] else: # Implicit field creation (class Meta 'fields' or 'additional') if obj: attribute_type = None try: if isinstance(obj, Mapping): attribute_type = type(obj[key]) else: attribute_type = type(getattr(obj, key)) except (AttributeError, KeyError) as err: err_type = type(err) raise err_type( '"{0}" is not a valid field for {1}.'.format(key, obj)) field_obj = self.TYPE_MAPPING.get(attribute_type, fields.Field)() else: # Object is None field_obj = fields.Field() # map key -> field (default to Raw) ret[key] = field_obj return ret def _invoke_dump_processors(self, tag_name, data, many, 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_name, pass_many=False, data=data, many=many, original_data=original_data) data = self._invoke_processors(tag_name, pass_many=True, data=data, many=many, original_data=original_data) return data def _invoke_load_processors(self, tag_name, data, many, original_data=None): # This has to invert the order of the dump processors, so run the pass_many # processors first. data = self._invoke_processors(tag_name, pass_many=True, data=data, many=many, original_data=original_data) data = self._invoke_processors(tag_name, pass_many=False, data=data, many=many, original_data=original_data) return data def _invoke_field_validators(self, data, many): for attr_name in self.__processors__[(VALIDATES, False)]: validator = getattr(self, attr_name) validator_kwargs = validator.__marshmallow_kwargs__[(VALIDATES, False)] field_name = validator_kwargs['field_name'] try: field_obj = self.fields[field_name] except KeyError: if field_name in self.declared_fields: continue raise ValueError('"{0}" field does not exist.'.format(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._unmarshal.call_and_store( getter_func=validator, data=value, field_name=field_name, field_obj=field_obj, index=(idx if self.opts.index_errors else None) ) if validated_value is missing: data[idx].pop(field_name, None) else: try: value = data[field_obj.attribute or field_name] except KeyError: pass else: validated_value = self._unmarshal.call_and_store( getter_func=validator, data=value, field_name=field_name, field_obj=field_obj ) if validated_value is missing: data.pop(field_name, None) def _invoke_validators(self, pass_many, data, original_data, many, field_errors=False): errors = {} for attr_name in self.__processors__[(VALIDATES_SCHEMA, pass_many)]: validator = getattr(self, attr_name) validator_kwargs = validator.__marshmallow_kwargs__[(VALIDATES_SCHEMA, pass_many)] pass_original = validator_kwargs.get('pass_original', False) skip_on_field_errors = validator_kwargs['skip_on_field_errors'] if skip_on_field_errors and field_errors: continue if pass_many: validator = functools.partial(validator, many=many) if many and not pass_many: for idx, item in enumerate(data): try: self._unmarshal.run_validator(validator, item, original_data, self.fields, many=many, index=idx, pass_original=pass_original) except ValidationError as err: errors.update(err.messages) else: try: self._unmarshal.run_validator(validator, data, original_data, self.fields, many=many, pass_original=pass_original) except ValidationError as err: errors.update(err.messages) if errors: raise ValidationError(errors) return None def _invoke_processors(self, tag_name, pass_many, data, many, original_data=None): for attr_name in self.__processors__[(tag_name, pass_many)]: # This will be a bound method. processor = getattr(self, attr_name) processor_kwargs = processor.__marshmallow_kwargs__[(tag_name, pass_many)] pass_original = processor_kwargs.get('pass_original', False) if pass_many: if pass_original: data = utils.if_none(processor(data, many, original_data), data) else: data = utils.if_none(processor(data, many), data) elif many: if pass_original: data = [utils.if_none(processor(item, original_data), item) for item in data] else: data = [utils.if_none(processor(item), item) for item in data] else: if pass_original: data = utils.if_none(processor(data, original_data), data) else: data = utils.if_none(processor(data), data) return data class Schema(with_metaclass(SchemaMeta, BaseSchema)): __doc__ = BaseSchema.__doc__ marshmallow-3.0.0b3/marshmallow/utils.py000077500000000000000000000277071314633611600203510ustar00rootroot00000000000000# -*- coding: utf-8 -*- """Utility methods for marshmallow.""" from __future__ import absolute_import, unicode_literals import collections import functools import datetime import inspect import json import re import time import types from calendar import timegm from decimal import Decimal, ROUND_HALF_EVEN, Context, Inexact from email.utils import formatdate, parsedate from pprint import pprint as py_pprint from marshmallow.compat import binary_type, text_type dateutil_available = False try: from dateutil import parser dateutil_available = True except ImportError: dateutil_available = False class _Missing(object): def __bool__(self): return False __nonzero__ = __bool__ # PY2 compat def __repr__(self): return '' # Singleton value that indicates that a field's value is missing from input # dict passed to :meth:`Schema.load`. If the field's value is not required, # it's ``default`` value is used. missing = _Missing() def is_generator(obj): """Return True if ``obj`` is a generator """ return inspect.isgeneratorfunction(obj) or inspect.isgenerator(obj) def is_iterable_but_not_string(obj): """Return True if ``obj`` is an iterable object that isn't a string.""" return ( (isinstance(obj, collections.Iterable) and not hasattr(obj, "strip")) or is_generator(obj) ) def is_indexable_but_not_string(obj): """Return True if ``obj`` is indexable but isn't a string.""" return not hasattr(obj, "strip") and hasattr(obj, "__getitem__") def is_collection(obj): """Return True if ``obj`` is a collection type, e.g list, tuple, queryset.""" return is_iterable_but_not_string(obj) and not isinstance(obj, collections.Mapping) def is_instance_or_subclass(val, class_): """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): """Return True if ``obj`` has keyed tuple behavior, such as namedtuples or SQLAlchemy's KeyedTuples. """ return isinstance(obj, tuple) and hasattr(obj, '_fields') def float_to_decimal(f): """Convert a floating point number to a Decimal with no loss of information. See: http://docs.python.org/release/2.6.7/library/decimal.html#decimal-faq """ n, d = f.as_integer_ratio() numerator, denominator = Decimal(n), Decimal(d) ctx = Context(prec=60) result = ctx.divide(numerator, denominator) while ctx.flags[Inexact]: ctx.flags[Inexact] = False ctx.prec *= 2 result = ctx.divide(numerator, denominator) return result ZERO_DECIMAL = Decimal() def decimal_to_fixed(value, precision): """Convert a `Decimal` to a fixed-precision number as a string.""" return text_type(value.quantize(precision, rounding=ROUND_HALF_EVEN)) def to_marshallable_type(obj, field_names=None): """Helper for converting an object to a dictionary only if it is not dictionary already or an indexable object nor a simple type""" if obj is None: return None # make it idempotent for None if hasattr(obj, '__marshallable__'): return obj.__marshallable__() if hasattr(obj, '__getitem__') and not is_keyed_tuple(obj): return obj # it is indexable it is ok if isinstance(obj, types.GeneratorType): return list(obj) if field_names: # exclude field names that aren't actual attributes of the object attrs = set(dir(obj)) & set(field_names) else: attrs = set(dir(obj)) return dict([(attr, getattr(obj, attr, None)) for attr in attrs if not attr.startswith("__") and not attr.endswith("__")]) def pprint(obj, *args, **kwargs): """Pretty-printing function that can pretty-print OrderedDicts like regular dictionaries. Useful for printing the output of :meth:`marshmallow.Schema.dump`. """ if isinstance(obj, collections.OrderedDict): print(json.dumps(obj, *args, **kwargs)) else: py_pprint(obj, *args, **kwargs) # From pytz: http://pytz.sourceforge.net/ ZERO = datetime.timedelta(0) HOUR = datetime.timedelta(hours=1) class UTC(datetime.tzinfo): """UTC Optimized UTC implementation. It unpickles using the single module global instance defined beneath this class declaration. """ zone = "UTC" _utcoffset = ZERO _dst = ZERO _tzname = zone def fromutc(self, dt): if dt.tzinfo is None: return self.localize(dt) return super(utc.__class__, self).fromutc(dt) def utcoffset(self, dt): return ZERO def tzname(self, dt): return "UTC" def dst(self, dt): return ZERO def localize(self, dt, is_dst=False): '''Convert naive time to local time''' if dt.tzinfo is not None: raise ValueError('Not naive datetime (tzinfo is already set)') return dt.replace(tzinfo=self) def normalize(self, dt, is_dst=False): '''Correct the timezone information on the given datetime''' if dt.tzinfo is self: return dt if dt.tzinfo is None: raise ValueError('Naive time - no tzinfo set') return dt.astimezone(self) def __repr__(self): return "" def __str__(self): return "UTC" UTC = utc = UTC() # UTC is a singleton def local_rfcformat(dt): """Return the RFC822-formatted representation of a timezone-aware datetime with the UTC offset. """ weekday = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"][dt.weekday()] month = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"][dt.month - 1] tz_offset = dt.strftime("%z") return "%s, %02d %s %04d %02d:%02d:%02d %s" % (weekday, dt.day, month, dt.year, dt.hour, dt.minute, dt.second, tz_offset) def rfcformat(dt, localtime=False): """Return the RFC822-formatted representation of a datetime object. :param datetime dt: The datetime. :param bool localtime: If ``True``, return the date relative to the local timezone instead of UTC, displaying the proper offset, e.g. "Sun, 10 Nov 2013 08:23:45 -0600" """ if not localtime: return formatdate(timegm(dt.utctimetuple())) else: return local_rfcformat(dt) # From Django _iso8601_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})?)?$' ) def isoformat(dt, localtime=False, *args, **kwargs): """Return the ISO8601-formatted UTC representation of a datetime object. """ if localtime and dt.tzinfo is not None: localized = dt else: if dt.tzinfo is None: localized = UTC.localize(dt) else: localized = dt.astimezone(UTC) return localized.isoformat(*args, **kwargs) def from_datestring(datestring): """Parse an arbitrary datestring and return a datetime object using dateutils' parser. """ if dateutil_available: return parser.parse(datestring) else: raise RuntimeError('from_datestring requires the python-dateutil library') def from_rfc(datestring, use_dateutil=True): """Parse a RFC822-formatted datetime string and return a datetime object. Use dateutil's parser if possible. https://stackoverflow.com/questions/885015/how-to-parse-a-rfc-2822-date-time-into-a-python-datetime """ # Use dateutil's parser if possible if dateutil_available and use_dateutil: return parser.parse(datestring) else: parsed = parsedate(datestring) # as a tuple timestamp = time.mktime(parsed) return datetime.datetime.fromtimestamp(timestamp) def from_iso(datestring, use_dateutil=True): """Parse an ISO8601-formatted datetime string and return a datetime object. Use dateutil's parser if possible and return a timezone-aware datetime. """ if not _iso8601_re.match(datestring): raise ValueError('Not a valid ISO8601-formatted datetime string') # Use dateutil's parser if possible if dateutil_available and use_dateutil: return parser.parse(datestring) else: # Strip off timezone info. return datetime.datetime.strptime(datestring[:19], '%Y-%m-%dT%H:%M:%S') def from_iso_time(timestring, use_dateutil=True): """Parse an ISO8601-formatted datetime string and return a datetime.time object. """ if dateutil_available and use_dateutil: return parser.parse(timestring).time() else: if len(timestring) > 8: # has microseconds fmt = '%H:%M:%S.%f' else: fmt = '%H:%M:%S' return datetime.datetime.strptime(timestring, fmt).time() def from_iso_date(datestring, use_dateutil=True): if dateutil_available and use_dateutil: return parser.parse(datestring).date() else: return datetime.datetime.strptime(datestring[:10], '%Y-%m-%d').date() def ensure_text_type(val): if isinstance(val, binary_type): val = val.decode('utf-8') return text_type(val) def pluck(dictlist, key): """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, default=missing): """Helper for pulling a keyed value off various types of objects""" if isinstance(key, int): return _get_value_for_key(obj, key, default) else: return _get_value_for_keys(obj, key.split('.'), default) def _get_value_for_keys(obj, keys, default): if len(keys) == 1: return _get_value_for_key(obj, keys[0], default) else: return _get_value_for_keys( _get_value_for_key(obj, keys[0], default), keys[1:], default) def _get_value_for_key(obj, key, default): try: return obj[key] except (KeyError, AttributeError, IndexError, TypeError): try: return getattr(obj, key) except AttributeError: return default return default def set_value(dct, key, value): """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( 'Cannot set {key} in {head} ' 'due to existing value: {target}'.format(key=key, head=head, target=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:`ValueError`. """ if not callable(obj): raise ValueError('Object {0!r} is not callable.'.format(obj)) return obj def _signature(func): if hasattr(inspect, 'signature'): return list(inspect.signature(func).parameters.keys()) if hasattr(func, '__self__'): # Remove bound arg to match inspect.signature() return inspect.getargspec(func).args[1:] # All args are unbound return inspect.getargspec(func).args def get_func_args(func): """Given a callable, return a tuple of argument names. Handles `functools.partial` objects and class-based callables. .. versionchanged:: 3.0.0a1 Do not return bound arguments, eg. ``self``. """ if isinstance(func, functools.partial): return _signature(func.func) if inspect.isfunction(func) or inspect.ismethod(func): return _signature(func) # Callable class return _signature(func.__call__) def if_none(value, default): return value if value is not None else default marshmallow-3.0.0b3/marshmallow/validate.py000066400000000000000000000401201314633611600207570ustar00rootroot00000000000000# -*- coding: utf-8 -*- """Validation classes for various types of data.""" from __future__ import unicode_literals import re from operator import attrgetter from marshmallow.compat import basestring, text_type, zip_longest from marshmallow.exceptions import ValidationError class Validator(object): """Base abstract class for validators. .. note:: This class does not provide any behavior. It is only used to add a useful `__repr__` implementation for validators. """ def __repr__(self): args = self._repr_args() args = '{0}, '.format(args) if args else '' return ( '<{self.__class__.__name__}({args}error={self.error!r})>' .format(self=self, args=args) ) def _repr_args(self): """A string representation of the args passed to this validator. Used by `__repr__`. """ return '' class URL(Validator): """Validate a URL. :param bool relative: Whether to allow relative URLs. :param str error: Error message to raise in case of a validation error. Can be interpolated with `{input}`. :param set schemes: Valid schemes. By default, ``http``, ``https``, ``ftp``, and ``ftps`` are allowed. """ URL_REGEX = re.compile( r'^(?:[a-z0-9\.\-\+]*)://' # scheme is validated separately r'(?:[^:@]+?:[^:@]*?@|)' # basic auth r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+' r'(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|' # domain... r'localhost|' # localhost... r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|' # ...or ipv4 r'\[?[A-F0-9]*:[A-F0-9:]+\]?)' # ...or ipv6 r'(?::\d+)?' # optional port r'(?:/?|[/?]\S+)$', re.IGNORECASE) RELATIVE_URL_REGEX = re.compile( r'^((?:[a-z0-9\.\-\+]*)://' # scheme is validated separately r'(?:[^:@]+?:[^:@]*?@|)' # basic auth r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+' r'(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|' # domain... r'localhost|' # localhost... r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|' # ...or ipv4 r'\[?[A-F0-9]*:[A-F0-9:]+\]?)' # ...or ipv6 r'(?::\d+)?)?' # optional port r'(?:/?|[/?]\S+)$', re.IGNORECASE) # host is optional, allow for relative URLs default_message = 'Not a valid URL.' default_schemes = set(['http', 'https', 'ftp', 'ftps']) # TODO; Switch position of `error` and `schemes` in 3.0 def __init__(self, relative=False, error=None, schemes=None): self.relative = relative self.error = error or self.default_message self.schemes = schemes or self.default_schemes def _repr_args(self): return 'relative={0!r}'.format(self.relative) def _format_error(self, value): return self.error.format(input=value) def __call__(self, value): message = self._format_error(value) if not value: raise ValidationError(message) # Check first if the scheme is valid if '://' in value: scheme = value.split('://')[0].lower() if scheme not in self.schemes: raise ValidationError(message) regex = self.RELATIVE_URL_REGEX if self.relative else self.URL_REGEX if not regex.search(value): raise ValidationError(message) return value class Email(Validator): """Validate an email address. :param str error: Error message to raise in case of a validation error. Can be interpolated with `{input}`. """ USER_REGEX = re.compile( r"(^[-!#$%&'*+/=?^`{}|~\w]+(\.[-!#$%&'*+/=?^`{}|~\w]+)*$" # dot-atom # quoted-string r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]' r'|\\[\001-\011\013\014\016-\177])*"$)', 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,})$' # 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}\]$', re.IGNORECASE | re.UNICODE) DOMAIN_WHITELIST = ('localhost',) default_message = 'Not a valid email address.' def __init__(self, error=None): self.error = error or self.default_message def _format_error(self, value): return self.error.format(input=value) def __call__(self, value): 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 it is passed is greater or equal to ``min`` and less than or equal to ``max``. 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. :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 str error: Error message to raise in case of a validation error. Can be interpolated with `{input}`, `{min}` and `{max}`. """ message_min = 'Must be at least {min}.' message_max = 'Must be at most {max}.' message_all = 'Must be between {min} and {max}.' def __init__(self, min=None, max=None, error=None): self.min = min self.max = max self.error = error def _repr_args(self): return 'min={0!r}, max={1!r}'.format(self.min, self.max) def _format_error(self, value, message): return (self.error or message).format(input=value, min=self.min, max=self.max) def __call__(self, value): if self.min is not None and 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: message = self.message_max if self.min is None else self.message_all raise ValidationError(self._format_error(value, message)) return value 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 int min: The minimum length. If not provided, minimum length will not be checked. :param int max: The maximum length. If not provided, maximum length will not be checked. :param int equal: The exact length. If provided, maximum and minimum length will not be checked. :param str 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=None, max=None, error=None, equal=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): return 'min={0!r}, max={1!r}, equal={2!r}'.format(self.min, self.max, self.equal) def _format_error(self, value, message): return (self.error or message).format(input=value, min=self.min, max=self.max, equal=self.equal) def __call__(self, value): 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 str 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=None): self.comparable = comparable self.error = error or self.default_message def _repr_args(self): return 'comparable={0!r}'.format(self.comparable) def _format_error(self, value): return self.error.format(input=value, other=self.comparable) def __call__(self, value): if value != self.comparable: raise ValidationError(self._format_error(value)) return value class Regexp(Validator): """Validate ``value`` against the provided regex. :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 str 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, flags=0, error=None): self.regex = re.compile(regex, flags) if isinstance(regex, basestring) else regex self.error = error or self.default_message def _repr_args(self): return 'regex={0!r}'.format(self.regex) def _format_error(self, value): return self.error.format(input=value, regex=self.regex.pattern) 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 str method: The name of the method to invoke. :param str 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, error=None, **kwargs): self.method = method self.error = error or self.default_message self.kwargs = kwargs def _repr_args(self): return 'method={0!r}, kwargs={1!r}'.format(self.method, self.kwargs) def _format_error(self, value): return self.error.format(input=value, method=self.method) def __call__(self, value): 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 iterable: A sequence of invalid values. :param str 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, error=None): self.iterable = iterable self.values_text = ', '.join(text_type(each) for each in self.iterable) self.error = error or self.default_message def _repr_args(self): return 'iterable={0!r}'.format(self.iterable) def _format_error(self, value): return self.error.format( input=value, values=self.values_text, ) def __call__(self, value): 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 iterable choices: A sequence of valid values. :param iterable labels: Optional sequence of labels to pair with the choices. :param str error: Error message to raise in case of a validation error. Can be interpolated with `{input}`, `{choices}` and `{labels}`. """ default_message = 'Not a valid choice.' def __init__(self, choices, labels=None, error=None): self.choices = choices self.choices_text = ', '.join(text_type(choice) for choice in self.choices) self.labels = labels if labels is not None else [] self.labels_text = ', '.join(text_type(label) for label in self.labels) self.error = error or self.default_message def _repr_args(self): return 'choices={0!r}, labels={1!r}'.format(self.choices, self.labels) def _format_error(self, value): return self.error.format( input=value, choices=self.choices_text, labels=self.labels_text, ) def __call__(self, value): try: if value not in self.choices: raise ValidationError(self._format_error(value)) except TypeError: raise ValidationError(self._format_error(value)) return value def options(self, valuegetter=text_type): """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 `unicode()`. """ 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 iterable choices: Same as :class:`OneOf`. :param iterable labels: Same as :class:`OneOf`. :param str 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 acceptable.' def _format_error(self, value): value_text = ', '.join(text_type(val) for val in value) return super(ContainsOnly, self)._format_error(value_text) def __call__(self, value): # 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 marshmallow-3.0.0b3/performance/000077500000000000000000000000001314633611600165725ustar00rootroot00000000000000marshmallow-3.0.0b3/performance/benchmark.py000066400000000000000000000077761314633611600211170ustar00rootroot00000000000000"""Simple benchmark for Marshmallow serialization of a moderately complex object. Uses the `timeit` module to benchmark serializing an object through Marshmallow. """ from __future__ import print_function, unicode_literals, division import argparse import cProfile import gc import timeit import time from marshmallow import Schema, fields, ValidationError, pre_load # 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('full_name') def full_name(self, obj): return obj.first + ' ' + obj.last def format_name(self, author): return "{0}, {1}".format(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.Int(dump_only=True) book_name = fields.Str() page_number = fields.Float() line_number = fields.Float() col_number = fields.Float() # 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): author_name = data.get('author') if author_name: first, last = author_name.split(' ') author_dict = dict(first=first, last=last) else: author_dict = {} data['author'] = author_dict return data class Author(object): 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(object): 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') usec = best * 1e6 / iterations return usec 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 = [] for i in range(args.object_count): quotes.append( Quote(i, Author(i, 'Foo', 'Bar', 42, 66, '123 Fake St'), 'Hello World', time.time(), 'The World', 34, 3, 70) ) print('Benchmark Result: {0:.2f} usec/dump'.format( run_timeit(quotes, args.iterations, args.repeat, profile=args.profile))) if __name__ == '__main__': main() marshmallow-3.0.0b3/setup.cfg000066400000000000000000000005251314633611600161140ustar00rootroot00000000000000[wheel] universal = 1 [flake8] ignore = E127,E128,E265,E302,N803,N804,N806,E266,E731 max-line-length = 100 exclude = .git,.ropeproject,.tox,docs,.git,examples/,tests/test_py3,marshmallow/compat.py,marshmallow/ordereddict.py,build,setup.py,env,venv [tool:pytest] norecursedirs = .git .ropeproject .tox docs env venv addopts = -v --tb=short marshmallow-3.0.0b3/setup.py000066400000000000000000000041231314633611600160030ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- import re from setuptools import setup, find_packages EXTRA_REQUIREMENTS = ['python-dateutil', 'simplejson'] def find_version(fname): """Attempts to find the version number in the file names fname. Raises RuntimeError if not found. """ version = '' with open(fname, 'r') as fp: reg = re.compile(r'__version__ = [\'"]([^\'"]*)[\'"]') for line in fp: m = reg.match(line) if m: version = m.group(1) break if not version: raise RuntimeError('Cannot find version information') return version __version__ = find_version("marshmallow/__init__.py") def read(fname): with open(fname) as fp: content = fp.read() return content setup( name='marshmallow', version=__version__, description=('A lightweight library for converting complex ' 'datatypes to and from native Python datatypes.'), long_description=read('README.rst'), author='Steven Loria', author_email='sloria1@gmail.com', url='https://github.com/marshmallow-code/marshmallow', packages=find_packages(exclude=('test*', 'examples')), package_dir={'marshmallow': 'marshmallow'}, include_package_data=True, extras_require={'reco': EXTRA_REQUIREMENTS}, license='MIT', zip_safe=False, keywords=('serialization', 'rest', 'json', 'api', 'marshal', 'marshalling', 'deserialization', 'validation', 'schema'), classifiers=[ 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', 'License :: OSI Approved :: MIT License', 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: PyPy', ], test_suite='tests' ) marshmallow-3.0.0b3/tasks.py000066400000000000000000000045341314633611600157760ustar00rootroot00000000000000# -*- coding: utf-8 -*- import os import sys import webbrowser from invoke import task docs_dir = 'docs' build_dir = os.path.join(docs_dir, '_build') @task def test(ctx, watch=False, last_failing=False): """Run the tests. Note: --watch requires pytest-xdist to be installed. """ import pytest flake(ctx) args = [] if watch: args.append('-f') if last_failing: args.append('--lf') if int(sys.version_info[0]) < 3: args.append('--ignore={0}'.format(os.path.join('tests', 'test_py3'))) retcode = pytest.main(args) benchmark(ctx) sys.exit(retcode) @task def flake(ctx): """Run flake8 on codebase.""" ctx.run('flake8 .', echo=True) @task def benchmark(ctx): """Run fast benchmark on codebase.""" # Explicitly shell out to get more consistent results by creating a new process # every time, for example running out of process ensures a pristine class_registry. ctx.run('python performance/benchmark.py --iterations=100 --repeat=3', echo=True) @task def clean(ctx): ctx.run("rm -rf build") ctx.run("rm -rf dist") ctx.run("rm -rf marshmallow.egg-info") clean_docs(ctx) print("Cleaned up.") @task def clean_docs(ctx): ctx.run("rm -rf %s" % build_dir) @task def browse_docs(ctx): path = os.path.join(build_dir, 'index.html') webbrowser.open_new_tab(path) def build_docs(ctx, browse): ctx.run("sphinx-build %s %s" % (docs_dir, build_dir), echo=True) if browse: browse_docs(ctx) @task def docs(ctx, clean=False, browse=False, watch=False): """Build the docs.""" if clean: clean_docs(ctx) if watch: watch_docs(ctx, browse=browse) else: build_docs(ctx, browse=browse) @task def watch_docs(ctx, browse=False): """Run build the docs when a file changes.""" try: import sphinx_autobuild # noqa except ImportError: print('ERROR: watch task requires the sphinx_autobuild package.') print('Install it with:') print(' pip install sphinx-autobuild') sys.exit(1) ctx.run('sphinx-autobuild {0} {1} {2} -z marshmallow'.format( '--open-browser' if browse else '', docs_dir, build_dir), echo=True, pty=True) @task def readme(ctx, browse=False): ctx.run("rst2html.py README.rst > README.html") if browse: webbrowser.open_new_tab('README.html') marshmallow-3.0.0b3/tests/000077500000000000000000000000001314633611600154335ustar00rootroot00000000000000marshmallow-3.0.0b3/tests/__init__.py000066400000000000000000000000301314633611600175350ustar00rootroot00000000000000# -*- coding: utf-8 -*- marshmallow-3.0.0b3/tests/base.py000066400000000000000000000203201314633611600167140ustar00rootroot00000000000000# -*- coding: utf-8 -*- """Test utilities and fixtures.""" import datetime as dt import uuid import simplejson import pytz from marshmallow import Schema, fields, post_load, validate, missing from marshmallow.compat import text_type from marshmallow.exceptions import ValidationError central = pytz.timezone("US/Central") ALL_FIELDS = [ fields.String, fields.Integer, fields.Boolean, fields.Float, fields.Number, fields.DateTime, fields.LocalDateTime, fields.Time, fields.Date, fields.TimeDelta, fields.Dict, fields.Url, fields.Email, fields.FormattedString, fields.UUID, fields.Decimal, ] ##### Custom asserts ##### def assert_almost_equal(a, b, precision=5): assert round(a, precision) == round(b, precision) def assert_date_equal(d1, d2): assert d1.year == d2.year assert d1.month == d2.month assert d1.day == d2.day def assert_datetime_equal(dt1, dt2): assert_date_equal(dt1, dt2) assert dt1.hour == dt2.hour assert dt1.minute == dt2.minute def assert_time_equal(t1, t2, microseconds=True): assert t1.hour == t2.hour assert t1.minute == t2.minute assert t1.second == t2.second if microseconds: assert t1.microsecond == t2.microsecond ##### Models ##### class User(object): SPECIES = "Homo sapiens" def __init__(self, name, age=0, id_=None, homepage=None, email=None, registered=True, time_registered=None, birthdate=None, balance=100, sex='male', 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 = central.localize( dt.datetime(2013, 11, 10, 14, 20, 58), is_dst=False ) self.id = id_ self.homepage = homepage self.email = email self.balance = balance self.registered = True self.hair_colors = ['black', 'brown', 'blond', 'redhead'] self.sex_choices = ('male', 'female') 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.sex = sex self.employer = employer self.relatives = [] self.various_data = various_data or {'pets': ['cat', 'dog'], 'address': "1600 Pennsylvania Ave\n" "Washington, DC 20006"} @property def since_created(self): return dt.datetime(2013, 11, 24) - self.created def __repr__(self): return "".format(self.name) class Blog(object): 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(object): def __init__(self, foo): self.foo = foo def __eq__(self, other): return self.foo == other.foo def __str__(self): return 'bar {0}'.format(self.foo) ###### Schemas ##### class Uppercased(fields.Field): """Custom field formatting example.""" def _serialize(self, value, attr, obj): if value: return value.upper() def get_lowername(obj): if obj is None: return missing if isinstance(obj, dict): return obj.get('name').lower() else: return obj.name.lower() class UserSchema(Schema): name = fields.String() age = fields.Float() created = fields.DateTime() created_formatted = fields.DateTime(format="%Y-%m-%d", attribute="created") created_iso = fields.DateTime(format="iso", attribute="created") updated = fields.DateTime() updated_local = fields.LocalDateTime(attribute="updated") species = fields.String(attribute="SPECIES") id = fields.String(default='no-id') uppername = Uppercased(attribute='name') homepage = fields.Url() email = fields.Email() balance = fields.Decimal() is_old = 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() since_created = fields.TimeDelta() sex = fields.Str(validate=validate.OneOf(['male', 'female'])) 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(text_type(te)) @post_load def make_user(self, data): return User(**data) class UserMetaSchema(Schema): """The equivalent of the UserSchema, using the ``fields`` option.""" uppername = Uppercased(attribute='name') balance = fields.Decimal() is_old = fields.Method("get_is_old") lowername = fields.Function(get_lowername) updated_local = fields.LocalDateTime(attribute="updated") 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(text_type(te)) class Meta: fields = ('name', 'age', 'created', 'updated', 'id', 'homepage', 'uppername', 'email', 'balance', 'is_old', 'lowername', "updated_local", "species", 'registered', 'hair_colors', 'sex_choices', "finger_count", 'uid', 'time_registered', 'birthdate', 'since_created', 'various_data') class UserExcludeSchema(UserSchema): class Meta: exclude = ("created", "updated", "field_not_found_but_thats_ok") 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.Nested(UserSchema, many=True) categories = fields.List(fields.String) id = fields.String() class BlogUserMetaSchema(Schema): user = fields.Nested(UserMetaSchema()) collaborators = fields.Nested(UserMetaSchema, many=True) class BlogSchemaMeta(Schema): '''Same as BlogSerializer but using ``fields`` options.''' user = fields.Nested(UserSchema) collaborators = fields.Nested(UserSchema, many=True) class Meta: fields = ('title', 'user', 'collaborators', 'categories', "id") class BlogOnlySchema(Schema): title = fields.String() user = fields.Nested(UserSchema) collaborators = fields.Nested(UserSchema, only=("id", ), many=True) class BlogSchemaExclude(BlogSchema): user = fields.Nested(UserSchema, exclude=("uppername", "species")) class BlogSchemaOnlyExclude(BlogSchema): user = fields.Nested(UserSchema, only=("name", ), exclude=("name", "species")) class BlogSchemaPrefixedUser(BlogSchema): user = fields.Nested(UserSchema(prefix="usr_")) collaborators = fields.Nested(UserSchema(prefix="usr_"), many=True) class mockjson(object): # noqa @staticmethod def dumps(val): return '{"foo": 42}'.encode('utf-8') @staticmethod def loads(val): return {'foo': 42} marshmallow-3.0.0b3/tests/conftest.py000066400000000000000000000010631314633611600176320ustar00rootroot00000000000000# -*- coding: utf-8 -*- """Pytest fixtures that are available in all test modules.""" import pytest from tests.base import User, UserSchema, Blog @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.0.0b3/tests/foo_serializer.py000066400000000000000000000001411314633611600210150ustar00rootroot00000000000000from marshmallow import Schema, fields class FooSerializer(Schema): _id = fields.Integer() marshmallow-3.0.0b3/tests/test_decorators.py000066400000000000000000000562121314633611600212170ustar00rootroot00000000000000# -*- coding: utf-8 -*- import pytest from marshmallow import ( Schema, fields, pre_dump, post_dump, pre_load, post_load, validates, validates_schema, ValidationError, ) def test_decorated_processors(): 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, return modified item. @pre_dump def increment_value(self, item): item['value'] += 1 return item # Implicit default raw, post dump, class method, modify in place. @post_dump def add_tag(self, item): item['value'] = self.TAG + item['value'] # Explicitly raw, post dump, instance method, return modified item. @post_dump(pass_many=True) def add_envelope(self, data, many): key = self.get_envelope_key(many) return {key: data} # Explicitly raw, pre load, instance method, return modified item. @pre_load(pass_many=True) def remove_envelope(self, data, many): 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, modify in place. @pre_load(pass_many=False) def remove_tag(self, item): item['value'] = item['value'][len(self.TAG):] # Explicit default raw, post load, instance method, modify in place. @post_load() def decrement_value(self, item): item['value'] -= 1 schema = ExampleSchema() # Need to re-create these because the processors will modify in place. make_item = lambda: {'value': 3} make_items = lambda: [make_item(), {'value': 5}] item_dumped = schema.dump(make_item()).data assert item_dumped == {'datum': {'value': 'TAG4'}} item_loaded = schema.load(item_dumped).data assert item_loaded == make_item() items_dumped = schema.dump(make_items(), many=True).data assert items_dumped == {'data': [{'value': 'TAG4'}, {'value': 'TAG6'}]} items_loaded = schema.load(items_dumped, many=True).data assert items_loaded == make_items() class TestPassOriginal: def test_pass_original_single_no_mutation(self): class MySchema(Schema): foo = fields.Field() @post_load(pass_original=True) def post_load(self, data, input_data): ret = data.copy() ret['_post_load'] = input_data['sentinel'] return ret @post_dump(pass_original=True) def post_dump(self, data, obj): ret = data.copy() ret['_post_dump'] = obj['sentinel'] return ret schema = MySchema() datum = {'foo': 42, 'sentinel': 24} item_loaded = schema.load(datum).data assert item_loaded['foo'] == 42 assert item_loaded['_post_load'] == 24 item_dumped = schema.dump(datum).data assert item_dumped['foo'] == 42 assert item_dumped['_post_dump'] == 24 def test_pass_original_single_with_mutation(self): class MySchema(Schema): foo = fields.Field() @post_load(pass_original=True) def post_load(self, data, input_data): data['_post_load'] = input_data['post_load'] schema = MySchema() item_loaded = schema.load({'foo': 42, 'post_load': 24}).data assert item_loaded['foo'] == 42 assert item_loaded['_post_load'] == 24 def test_pass_original_many(self): class MySchema(Schema): foo = fields.Field() @post_load(pass_many=True, pass_original=True) def post_load(self, data, many, original): 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, many, original): 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() data = [{'foo': 42, 'sentinel': 24}, {'foo': 424, 'sentinel': 242}] items_loaded = schema.load(data, many=True).data 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).data 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).data assert item_loaded == {'foo': 42, '_post_load': 24} item_dumped = schema.dump(datum, many=False).data assert item_dumped == {'foo': 42, '_post_dump': 24} def test_decorated_processor_inheritance(): class ParentSchema(Schema): @post_dump def inherited(self, item): item['inherited'] = 'inherited' return item @post_dump def overridden(self, item): item['overridden'] = 'base' return item @post_dump def deleted(self, item): item['deleted'] = 'retained' return item class ChildSchema(ParentSchema): @post_dump def overridden(self, item): item['overridden'] = 'overridden' return item deleted = None parent_dumped = ParentSchema().dump({}).data assert parent_dumped == { 'inherited': 'inherited', 'overridden': 'base', 'deleted': 'retained' } child_dumped = ChildSchema().dump({}).data 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(s, data): data['generated_field'] = 7 class Meta: # Removing generated_field from here drops it from the output fields = ('field', 'generated_field') assert Foo().dump({"field": 5}).data == {'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_and_strict(self): class VSchema(Schema): s = fields.String() @validates('s') def validate_string(self, data): raise ValidationError('nope') with pytest.raises(ValidationError) as excinfo: VSchema(strict=True).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_and_strict(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(strict=True).load({'s': 'foo'}) assert excinfo.value.messages == {'s': ['nope']} with pytest.raises(ValidationError): S1(strict=True, 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 == {} result, errors = schema.load({'foo': 41}) assert errors assert result == {} result, errors = schema.load([{'foo': 42}, {'foo': 43}], many=True) 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) as excinfo: schema.validate({'foo': 42}) assert '"bar" field does not exist.' in str(excinfo) 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' class TestValidatesSchemaDecorator: def test_validator_nested_many(self): class NestedSchema(Schema): foo = fields.Int(required=True) @validates_schema def validate_schema(self, data): raise ValidationError('This will never work', 'foo') 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 '_schema' in errors['nested'] assert 'foo' not in errors['nested'] def test_decorated_validators(self): class MySchema(Schema): foo = fields.Int() bar = fields.Int() @validates_schema def validate_schema(self, data): if data['foo'] <= 3: raise ValidationError('Must be greater than 3') @validates_schema(pass_many=True) def validate_raw(self, data, many): 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): 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): if data['foo'] <= 3: raise ValidationError('Must be greater than 3') @validates_schema def validate_bar(self, data): 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_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): if isinstance(original_data, dict) and isinstance(original_data['foo'], str): raise ValidationError('foo cannot be a string') # See https://github.com/marshmallow-code/marshmallow/issues/127 @validates_schema(pass_many=True, pass_original=True) def check_unknown_fields(self, data, original_data, many): def check(datum): for key, val in datum.items(): if key not in self.fields: raise ValidationError({'code': 'invalid_field'}) if many: for each in original_data: check(each) else: check(original_data) schema = MySchema() errors = schema.validate({'foo': 4, 'baz': 42}) assert '_schema' in errors assert len(errors['_schema']) == 1 assert errors['_schema'][0] == {'code': 'invalid_field'} errors = schema.validate({'foo': '4'}) assert '_schema' in errors assert len(errors['_schema']) == 1 assert errors['_schema'][0] == 'foo cannot be a string' schema = MySchema() errors = schema.validate([{'foo': 4, 'baz': 42}], many=True) assert '_schema' in errors assert len(errors['_schema']) == 1 assert errors['_schema'][0] == {'code': 'invalid_field'} # https://github.com/marshmallow-code/marshmallow/issues/273 def test_allow_arbitrary_field_names_in_error(self): class MySchema(Schema): foo = fields.Int() bar = fields.Int() @validates_schema(pass_original=True) def strict_fields(self, data, original_data): for key in original_data: if key not in self.fields: raise ValidationError('Unknown field name', key) schema = MySchema() errors = schema.validate({'foo': 2, 'baz': 42}) assert 'baz' in errors assert len(errors['baz']) == 1 assert errors['baz'][0] == 'Unknown field name' 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): 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): 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 def test_decorator_error_handling(): class ExampleSchema(Schema): foo = fields.Int() bar = fields.Int() @pre_load() def pre_load_error1(self, item): if item['foo'] != 0: return errors = { 'foo': ['preloadmsg1'], 'bar': ['preloadmsg2', 'preloadmsg3'], } raise ValidationError(errors) @pre_load() def pre_load_error2(self, item): if item['foo'] != 4: return raise ValidationError('preloadmsg1', 'foo') @pre_load() def pre_load_error3(self, item): if item['foo'] != 8: return raise ValidationError('preloadmsg1') @post_load() def post_load_error1(self, item): if item['foo'] != 1: return item errors = { 'foo': ['postloadmsg1'], 'bar': ['postloadmsg2', 'postloadmsg3'], } raise ValidationError(errors) @post_load() def post_load_error2(self, item): if item['foo'] != 5: return item raise ValidationError('postloadmsg1', 'foo') @pre_dump() def pre_dump_error1(self, item): if item['foo'] != 2: return errors = { 'foo': ['predumpmsg1'], 'bar': ['predumpmsg2', 'predumpmsg3'], } raise ValidationError(errors) @pre_dump() def pre_dump_error2(self, item): if item['foo'] != 6: return raise ValidationError('predumpmsg1', 'foo') @post_dump() def post_dump_error1(self, item): if item['foo'] != 3: return item errors = { 'foo': ['postdumpmsg1'], 'bar': ['postdumpmsg2', 'postdumpmsg3'], } raise ValidationError(errors) @post_dump() def post_dump_error2(self, item): if item['foo'] != 7: return raise ValidationError('postdumpmsg1', 'foo') def make_item(foo, bar): data, errors = schema.load({'foo': foo, 'bar': bar}) assert data is not None assert not errors return data schema = ExampleSchema() data, errors = schema.load({'foo': 0, 'bar': 1}) 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'] data, errors = schema.load({'foo': 1, 'bar': 1}) 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'] data, errors = schema.dump(make_item(2, 1)) assert 'foo' in errors assert len(errors['foo']) == 1 assert errors['foo'][0] == 'predumpmsg1' assert 'bar' in errors assert len(errors['bar']) == 2 assert 'predumpmsg2' in errors['bar'] assert 'predumpmsg3' in errors['bar'] data, errors = schema.dump(make_item(3, 1)) assert 'foo' in errors assert len(errors['foo']) == 1 assert errors['foo'][0] == 'postdumpmsg1' assert 'bar' in errors assert len(errors['bar']) == 2 assert 'postdumpmsg2' in errors['bar'] assert 'postdumpmsg3' in errors['bar'] data, errors = schema.load({'foo': 4, 'bar': 1}) assert len(errors) == 1 assert 'foo' in errors assert len(errors['foo']) == 1 assert errors['foo'][0] == 'preloadmsg1' data, errors = schema.load({'foo': 5, 'bar': 1}) assert len(errors) == 1 assert 'foo' in errors assert len(errors['foo']) == 1 assert errors['foo'][0] == 'postloadmsg1' data, errors = schema.dump(make_item(6, 1)) assert 'foo' in errors assert len(errors['foo']) == 1 assert errors['foo'][0] == 'predumpmsg1' data, errors = schema.dump(make_item(7, 1)) assert 'foo' in errors assert len(errors['foo']) == 1 assert errors['foo'][0] == 'postdumpmsg1' data, errors = schema.load({'foo': 8, 'bar': 1}) 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_strict_error_handling_with_load(decorator): class ExampleSchema(Schema): @decorator def raise_value_error(self, item): raise ValidationError({'foo': 'error'}) schema = ExampleSchema(strict=True) with pytest.raises(ValidationError) as exc: schema.load({}) assert exc.value.messages == {'foo': 'error'} schema.dump(object()) @pytest.mark.parametrize( 'decorator', [ pre_dump, post_dump, ] ) def test_decorator_strict_error_handling_with_dump(decorator): class ExampleSchema(Schema): @decorator def raise_value_error(self, item): raise ValidationError({'foo': 'error'}) schema = ExampleSchema(strict=True) with pytest.raises(ValidationError) as exc: schema.dump(object()) assert exc.value.messages == {'foo': 'error'} schema.load({}) marshmallow-3.0.0b3/tests/test_deserialization.py000066400000000000000000001436131314633611600222420ustar00rootroot00000000000000# -*- coding: utf-8 -*- import datetime as dt import uuid import decimal import pytest from marshmallow import fields, utils, Schema, validate from marshmallow.exceptions import ValidationError from marshmallow.compat import basestring from marshmallow.validate import Equal from tests.base import ( assert_almost_equal, assert_date_equal, assert_datetime_equal, assert_time_equal, central, ALL_FIELDS, ) class TestDeserializingNone: @pytest.mark.parametrize('FieldClass', ALL_FIELDS) def test_fields_allow_none_deserialize_to_none(self, FieldClass): if FieldClass == fields.FormattedString: field = FieldClass(src_str='foo', allow_none=True) else: field = FieldClass(allow_none=True) 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): # by default, allow_none=False if FieldClass == fields.FormattedString: field = FieldClass(src_str='foo') else: field = FieldClass() with pytest.raises(ValidationError) as excinfo: field.deserialize(None) assert 'Field may not be null.' in str(excinfo) def test_allow_none_is_true_if_missing_is_true(self): field = fields.Field(missing=None) assert field.allow_none is True field.deserialize(None) is None def test_list_field_deserialize_none_to_empty_list(self): field = fields.List(fields.String(allow_none=True), allow_none=True) assert field.deserialize(None) is None class TestFieldDeserialization: def test_float_field_deserialization(self): field = fields.Float() assert_almost_equal(field.deserialize('12.3'), 12.3) assert_almost_equal(field.deserialize(12.3), 12.3) @pytest.mark.parametrize('in_val', [ 'bad', '', {}, ]) 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_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) 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.' 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() and not m1d.is_signed() m2d = field.deserialize(m2) assert isinstance(m2d, decimal.Decimal) assert m2d.is_qnan() and not m2d.is_signed() m3d = field.deserialize(m3) assert isinstance(m3d, decimal.Decimal) assert m3d.is_qnan() and not m3d.is_signed() m4d = field.deserialize(m4) assert isinstance(m4d, decimal.Decimal) assert m4d.is_qnan() and not m4d.is_signed() m5d = field.deserialize(m5) assert isinstance(m5d, decimal.Decimal) assert m5d.is_infinite() and m5d.is_signed() m6d = field.deserialize(m6) assert isinstance(m6d, decimal.Decimal) assert m6d.is_infinite() and not m6d.is_signed() m7d = field.deserialize(m7) assert isinstance(m7d, decimal.Decimal) assert m7d.is_zero() and 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 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() and m7d.is_signed() 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(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 = set(['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 = set(['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 @pytest.mark.parametrize('in_value', [ 'not-a-datetime', 42, '', [], ]) def test_invalid_datetime_deserialization(self, in_value): field = fields.DateTime() with pytest.raises(ValidationError) as excinfo: field.deserialize(in_value) msg = 'Not a valid datetime.'.format(in_value) assert msg in str(excinfo) def test_datetime_passed_year_is_invalid(self): field = fields.DateTime() with pytest.raises(ValidationError): field.deserialize('1916') def test_datetime_passed_date_is_invalid(self): field = fields.DateTime() with pytest.raises(ValidationError): field.deserialize('2017-04-13') def test_custom_date_format_datetime_field_deserialization(self): dtime = dt.datetime.now() datestring = dtime.strftime('%H:%M:%S %Y-%m-%d') field = fields.DateTime(format='%d-%m-%Y %H:%M:%S') #deserialization should fail when datestring is not of same format with pytest.raises(ValidationError) as excinfo: field.deserialize(datestring) msg = 'Not a valid datetime.'.format(datestring) assert msg in str(excinfo) field = fields.DateTime(format='%H:%M:%S %Y-%m-%d') assert_datetime_equal(field.deserialize(datestring), dtime) field = fields.DateTime() assert msg in str(excinfo) @pytest.mark.parametrize('fmt', ['rfc', 'rfc822']) def test_rfc_datetime_field_deserialization(self, fmt): dtime = dt.datetime.now() datestring = utils.rfcformat(dtime) field = fields.DateTime(format=fmt) assert_datetime_equal(field.deserialize(datestring), dtime) @pytest.mark.parametrize('fmt', ['iso', 'iso8601']) def test_iso_datetime_field_deserialization(self, fmt): dtime = dt.datetime.now() datestring = dtime.isoformat() field = fields.DateTime(format=fmt) assert_datetime_equal(field.deserialize(datestring), dtime) def test_localdatetime_field_deserialization(self): dtime = dt.datetime.now() localized_dtime = central.localize(dtime) field = fields.DateTime(format='iso') result = field.deserialize(localized_dtime.isoformat()) assert_datetime_equal(result, dtime) # If dateutil is used, the datetime will not be naive if utils.dateutil_available: assert result.tzinfo is not None 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_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 @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.' def test_date_field_deserialization(self): field = fields.Date() d = dt.date(2014, 8, 21) iso_date = d.isoformat() result = field.deserialize(iso_date) assert isinstance(result, dt.date) assert_date_equal(result, d) @pytest.mark.parametrize('in_value', [ '', 123, [], ]) 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): field = fields.Dict() assert field.deserialize({"foo": "bar"}) == {"foo": "bar"} with pytest.raises(ValidationError) as excinfo: field.deserialize('baddict') assert excinfo.value.args[0] == 'Not a valid mapping type.' 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.' 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=set(['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.' 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'] ) 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 @pytest.mark.parametrize('in_value', [ 'malformed', 123, [], ]) 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_deserialization_function_must_be_callable(self): with pytest.raises(ValueError): 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') s = BadSchema() with pytest.raises(ValueError): s.fields['uppername'].deserialize('STEVE') def test_method_field_deserialize_only(self): class MethodDeserializeOnly(Schema): def lowercase_name(self, value): return value.lower() m = fields.Method(deserialize='lowercase_name') m.parent = MethodDeserializeOnly() assert m.deserialize('ALEC') == 'alec' def test_datetime_list_field_deserialization(self): dtimes = dt.datetime.now(), dt.datetime.now(), dt.datetime.utcnow() 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_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({'bar': 24}).data['foo'] == 42 assert sch.load({'foo': 24}).data['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) == ValidationError def test_field_deserialization_with_user_validator_class_that_returns_bool(self): class MyValidator(object): def __call__(self, val): if val == 'valid': return True return False field = fields.Field(validate=MyValidator()) assert field.deserialize('valid') == 'valid' with pytest.raises(ValidationError) as excinfo: field.deserialize('invalid') assert 'Invalid value.' in str(excinfo) 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.Field(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(u'привет') assert type(excinfo.value) == 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) as excinfo: field.deserialize('invalid') assert 'Invalid value.' in str(excinfo) 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) as excinfo: field.deserialize('invalid') assert 'Bad value.' in str(excinfo) # 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, errors = SimpleUserSchema().load(user_dict) assert result['name'] == 'Monty' assert_almost_equal(result['age'], 42.3) def test_deserialize_with_missing_values(self): user_dict = {'name': 'Monty'} result, errs = 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, errors = 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', )) result = schema.load({'name': 'Monty', 'age': 42}) assert 'name' in result.data assert 'age' not in result.data def test_nested_single_deserialization_to_dict(self): class SimpleBlogSerializer(Schema): title = fields.String() author = fields.Nested(SimpleUserSchema) blog_dict = { 'title': 'Gimme Shelter', 'author': {'name': 'Mick', 'age': '914', 'email': 'mick@stones.com'} } result, errors = 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, errors = 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.Nested(ANestedSchema, only='pk') sch = MainSchema() result = sch.load({'pk': '123', 'child': '456'}) assert len(result.errors) == 0 assert result.data['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.Nested(ANestedSchema, only='pk', many=True) sch = MainSchema() result = sch.load({'pk': '123', 'children': ['456', '789']}) assert len(result.errors) == 0 assert result.data['children'][0]['pk'] == '456' assert result.data['children'][1]['pk'] == '789' def test_none_deserialization(self): result, errors = SimpleUserSchema().load(None) assert result is None 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, errors = SimpleBlogSerializer().load(blog_dict) assert not errors 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, errors = 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.Field(attribute='bar.baz') schema = MySchema() dump_data, errors = schema.dump({'bar': {'baz': 42}}) assert dump_data == {'foo': 42} load_data, errors = 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' } result, errors = AliasingUserSerializer().load(data) assert errors assert errors['username'] == ['Not a valid email address.'] def test_deserialize_with_attribute_param_error_returns_load_from_not_attribute_name(self): class AliasingUserSerializer(Schema): name = fields.String(load_from='Name') username = fields.Email(attribute='email', load_from='UserName') years = fields.Integer(attribute='age', load_from='Years') data = { 'Name': 'Mick', 'UserName': 'foobar.com', 'years': 'abc' } result, errors = AliasingUserSerializer().load(data) assert errors['UserName'] == [u'Not a valid email address.'] assert errors['years'] == [u'Not a valid integer.'] def test_deserialize_with_load_from_param(self): class AliasingUserSerializer(Schema): name = fields.String(load_from='Name') username = fields.Email(attribute='email', load_from='UserName') years = fields.Integer(attribute='age', load_from='Years') data = { 'Name': 'Mick', 'UserName': 'foo@bar.com', 'years': '42' } result, errors = AliasingUserSerializer().load(data) assert result['name'] == 'Mick' assert result['email'] == 'foo@bar.com' assert result['age'] == 42 def test_deserialize_with_dump_only_param(self): class AliasingUserSerializer(Schema): name = fields.String() years = fields.Integer(dump_only=True) nicknames = fields.List(fields.Str(), dump_only=True) data = { 'name': 'Mick', 'years': '42', 'nicknames': ['Your Majesty', 'Brenda'] } result, errors = AliasingUserSerializer().load(data) assert result['name'] == 'Mick' assert 'years' not in result assert 'nicknames' not in result def test_deserialize_with_missing_param_value(self): class AliasingUserSerializer(Schema): name = fields.String() years = fields.Integer(missing=10) data = { 'name': 'Mick', } result, errors = AliasingUserSerializer().load(data) assert result['name'] == 'Mick' assert result['years'] == 10 def test_deserialize_with_missing_param_callable(self): class AliasingUserSerializer(Schema): name = fields.String() years = fields.Integer(missing=lambda: 13 + 7) data = { 'name': 'Mick', } result, errors = AliasingUserSerializer().load(data) assert result['name'] == 'Mick' assert result['years'] == 20 def test_deserialize_with_missing_param_none(self): class AliasingUserSerializer(Schema): name = fields.String() years = fields.Integer(missing=None, allow_none=True) data = { 'name': 'Mick', } result, errors = AliasingUserSerializer().load(data) assert not errors assert result['name'] == 'Mick' assert result['years'] is None def test_deserialization_returns_errors(self): bad_data = { 'email': 'invalid-email', 'colors': 'burger', 'age': -1, } v = Validator(strict=False) result, errors = v.load(bad_data) assert 'email' in errors assert 'colors' in errors assert 'age' in errors def test_deserialization_returns_errors_with_multiple_validators(self): bad_data = { 'email': 'invalid-email', 'colors': 'burger', 'age': -1, } v = Validators(strict=False) result, errors = v.load(bad_data) assert 'email' in errors assert 'colors' in errors assert 'age' in errors def test_strict_mode_deserialization(self): bad_data = { 'email': 'invalid-email', 'colors': 'burger', 'age': -1, } v = Validator(strict=True) with pytest.raises(ValidationError): v.load(bad_data) def test_strict_mode_many(self): bad_data = [ {'email': 'foo@bar.com', 'colors': 'red', 'age': 18}, {'email': 'bad', 'colors': 'pizza', 'age': -1} ] v = Validator(strict=True, many=True) with pytest.raises(ValidationError): v.load(bad_data) def test_strict_mode_deserialization_with_multiple_validators(self): bad_data = { 'email': 'invalid-email', 'colors': 'burger', 'age': -1, } v = Validators(strict=True) with pytest.raises(ValidationError): v.load(bad_data) def test_uncaught_validation_errors_are_stored(self): def validate_field(val): raise ValidationError('Something went wrong') class MySchema(Schema): foo = fields.Field(validate=validate_field) _, errors = MySchema().load({'foo': 42}) 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.Field(required=True, validate=[ validate_with_bool, validate_with_error, ]) _, errors = MySchema().load({'foo': 'bar'}) assert type(errors['foo']) == 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, ]) _, errors = MySchema().load({'email': 'foo'}) 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, ]) _, errors = MySchema().load({'url': 'foo'}) 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.Field(required=True, validate=lambda f: False) _, errors = MySchema().load({}) # 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.Field(required=True) bar = fields.Field(required=True) schema_args = {} load_args = {} if partial_schema: schema_args['partial'] = True else: load_args['partial'] = True data, errors = MySchema(**schema_args).load({'foo': 3}, **load_args) assert data['foo'] == 3 assert 'bar' not in data assert not errors def test_partial_fields_deserialization(self): class MySchema(Schema): foo = fields.Field(required=True) bar = fields.Field(required=True) baz = fields.Field(required=True) data, errors = MySchema().load({'foo': 3}, partial=tuple()) assert data['foo'] == 3 assert 'bar' in errors assert 'baz' in errors data, errors = MySchema().load({'foo': 3}, partial=('bar', 'baz')) assert data['foo'] == 3 assert 'bar' not in data assert 'baz' not in data assert not errors data, errors = MySchema(partial=True).load({'foo': 3}, partial=('bar', 'baz')) assert data['foo'] == 3 assert 'bar' not in data assert 'baz' not in data assert not errors def test_partial_fields_validation(self): class MySchema(Schema): foo = fields.Field(required=True) bar = fields.Field(required=True) baz = fields.Field(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 not errors errors = MySchema(partial=True).validate({'foo': 3}, partial=('bar', 'baz')) assert not errors validators_gen = (func for func in [lambda x: x <= 24, lambda x: 18 <= x]) 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: 18 <= x]), fields.Integer(validate=(lambda x: x <= 24, lambda x: 18 <= x, )), 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(strict=True).load({'name': 'joe'}) with pytest.raises(ValidationError) as excinfo: MethodSerializer(strict=True).load({'name': 'joseph'}) assert 'Invalid value.' in str(excinfo) # 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() data, errors = sch.load({'w': 90, 'n': {'x': 90, 'y': 89, 'z': None}}) assert 'z' in errors['n'] assert data == {'w': 90, 'n': {'x': 90, 'y': 89}} data, errors = sch.load({'w': 90, 'n': {'x': 90, 'y': -1, 'z': 180}}) 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.'] FIELDS_TO_TEST = [f for f in ALL_FIELDS if f not in [fields.FormattedString]] @pytest.mark.parametrize('FieldClass', FIELDS_TO_TEST) def test_required_field_failure(FieldClass): # noqa class RequireSchema(Schema): age = FieldClass(required=True) user_data = {"name": "Phil"} data, errs = RequireSchema().load(user_data) assert "Missing data for required field." in errs['age'] assert data == {} @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"} data, errs = RequireSchema().load(user_data) expected = [message] if isinstance(message, basestring) else message assert expected == errs['age'] assert data == {} # Regression test for https://github.com/marshmallow-code/marshmallow/issues/261 def test_deserialize_doesnt_raise_exception_if_strict_is_false_and_input_type_is_incorrect(): class MySchema(Schema): foo = fields.Field() bar = fields.Field() data, errs = MySchema().load([]) assert '_schema' in errs assert errs['_schema'] == ['Invalid input type.'] def test_deserialize_raises_exception_if_strict_is_true_and_input_type_is_incorrect(): class MySchema(Schema): foo = fields.Field() bar = fields.Field() with pytest.raises(ValidationError) as excinfo: MySchema(strict=True).load([]) assert 'Invalid input type.' in str(excinfo) exc = excinfo.value assert exc.field_names == ['_schema'] assert exc.fields == [] marshmallow-3.0.0b3/tests/test_exceptions.py000066400000000000000000000020071314633611600212240ustar00rootroot00000000000000# -*- coding: utf-8 -*- 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_names(self): err = ValidationError('invalid email', field_names='email') assert err.field_names == ['email'] err = ValidationError('invalid email', field_names=['email']) assert err.field_names == ['email'] def test_str(self): err = ValidationError('invalid email') assert str(err) == 'invalid email' err2 = ValidationError('invalid email', 'email') assert str(err2) == 'invalid email' marshmallow-3.0.0b3/tests/test_fields.py000066400000000000000000000153301314633611600203140ustar00rootroot00000000000000# -*- coding: utf-8 -*- import pytest from marshmallow import fields, Schema, ValidationError from marshmallow.marshalling import missing from tests.base import ALL_FIELDS, User class TestFieldAliases: def test_int_is_integer(self): assert fields.Int is fields.Integer def test_str_is_string(self): assert fields.Str is fields.String def test_bool_is_boolean(self): assert fields.Bool is fields.Boolean def test_URL_is_Url(self): # flake8: noqa assert fields.URL is fields.Url class TestField: def test_repr(self): default = u'œ∑´' field = fields.Field(default=default, attribute=None) assert repr(field) == (u'' .format(default, missing=missing, error_messages=field.error_messages)) 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_many_stores_error_indices(): s = UserSchema() u1, u2 = User('Mick', email='mick@stones.com'), User('Keith', email='invalid') _, errors = s.dump([u1, u2], many=True) assert 1 in errors assert len(errors[1]) == 1 assert 'email' in errors[1] def test_dump_many_doesnt_stores_error_indices_when_index_errors_is_false(): class NoIndex(Schema): email = fields.Email() class Meta: index_errors = False s = NoIndex() u1, u2 = User('Mick', email='mick@stones.com'), User('Keith', email='invalid') _, errors = s.dump([u1, u2], many=True) assert 1 not in errors assert 'email' in errors def test_dump_returns_a_marshalresult(user): s = UserSchema() result = s.dump(user) assert type(result) == MarshalResult data = result.data assert type(data) == dict errors = result.errors assert type(errors) == dict def test_dumps_returns_a_marshalresult(user): s = UserSchema() result = s.dumps(user) assert type(result) == MarshalResult assert type(result.data) == str assert type(result.errors) == dict def test_dumping_single_object_with_collection_schema(user): s = UserSchema(many=True) result = s.dump(user, many=False) assert type(result.data) == dict assert result.data == UserSchema().dump(user).data 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.data) == User assert result.data.name == UserSchema().load(in_data).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.data) assert len(data) == 2 assert data[0] == s.dump(u1).data def test_load_returns_an_unmarshalresult(): s = UserSchema() result = s.load({'name': 'Monty'}) assert type(result) == UnmarshalResult assert type(result.data) == User assert type(result.errors) == dict def test_load_many(): s = UserSchema() in_data = [{'name': 'Mick'}, {'name': 'Keith'}] result = s.load(in_data, many=True) assert type(result.data) == list assert type(result.data[0]) == User assert result.data[0].name == 'Mick' def test_loads_returns_an_unmarshalresult(user): s = UserSchema() result = s.loads(json.dumps({'name': 'Monty'})) assert type(result) == UnmarshalResult assert type(result.data) == User assert type(result.errors) == dict 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.data) == list assert result.data[0].name == 'Mick' def test_loads_deserializes_from_json(): user_dict = {'name': 'Monty', 'age': '42.3'} user_json = json.dumps(user_dict) result, errors = UserSchema().loads(user_json) assert isinstance(result, User) assert result.name == 'Monty' assert_almost_equal(result.age, 42.3) def test_serializing_none(): class MySchema(Schema): id = fields.Str(default='no-id') num = fields.Int() name = fields.Str() s = UserSchema().dump(None) assert s.data == {'id': 'no-id'} assert s.errors == {} 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).data) s_single.loads(s_single.dumps(u1).data) s_many.load(s_many.dump([u1, u2]).data) s_many.loads(s_many.dumps([u1, u2]).data) 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) 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_returns_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_strict(self): s = UserSchema(strict=True) with pytest.raises(ValidationError) as excinfo: s.validate({'email': 'bad-email'}) exc = excinfo.value assert exc.messages == {'email': ['Not a valid email address.']} assert type(exc.fields[0]) == fields.Email def test_validate_required(self): class MySchema(Schema): foo = fields.Field(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, errors = ser.dump(user) json_data, errors = ser.dumps(user) assert type(json_data) == 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.data['created'] == expected def test_datetime_formatted_field(user, serialized_user): result = serialized_user.data['created_formatted'] assert result == user.created.strftime("%Y-%m-%d") def test_datetime_iso_field(user, serialized_user): assert serialized_user.data['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.data['updated'] == expected def test_local_datetime_field(user, serialized_user): expected = utils.isoformat(user.updated, localtime=True) assert serialized_user.data['updated_local'] == expected def test_class_variable(serialized_user): assert serialized_user.data['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.data) == 2 assert serialized.data[0]['name'] == "Mick" assert serialized.data[1]['name'] == "Keith" def test_inheriting_schema(user): sch = ExtendedUserSchema() result = sch.dump(user) assert result.data['name'] == user.name user.is_old = True result = sch.dump(user) assert result.data['is_old'] is True def test_custom_field(serialized_user, user): assert serialized_user.data['uppername'] == user.name.upper() def test_url_field(serialized_user, user): assert serialized_user.data['homepage'] == user.homepage def test_relative_url_field(): u = {'name': 'John', 'homepage': '/foo'} result, errors = UserRelativeUrlSchema().load(u) assert 'homepage' not in errors @pytest.mark.parametrize('SchemaClass', [UserSchema, UserMetaSchema]) def test_stores_invalid_url_error(SchemaClass): user = {'name': 'Steve', 'homepage': 'www.foo.com'} result = SchemaClass().load(user) assert "homepage" in result.errors expected = ['Not a valid URL.'] assert result.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.data['email'] == "john@example.com" def test_stored_invalid_email(): u = {'name': 'John', 'email': 'johnexample.com'} s = UserSchema().load(u) assert "email" in s.errors assert s.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.data['age']) == int assert serialized.data['age'] == 42 def test_as_string(): u = User("John", age=42.3) serialized = UserFloatStringSchema().dump(u) assert type(serialized.data['age']) == str assert_almost_equal(float(serialized.data['age']), 42.3) @pytest.mark.parametrize('SchemaClass', [UserSchema, UserMetaSchema]) def test_method_field(SchemaClass, serialized_user): assert serialized_user.data['is_old'] is False u = User("Joe", age=81) assert SchemaClass().dump(u).data['is_old'] is True def test_function_field(serialized_user, user): assert serialized_user.data['lowername'] == user.name.lower() @pytest.mark.parametrize('SchemaClass', [UserSchema, UserMetaSchema]) def test_prefix(SchemaClass, user): s = SchemaClass(prefix="usr_").dump(user) assert s.data['usr_name'] == user.name def test_fields_must_be_declared_as_instances(user): class BadUserSchema(Schema): name = fields.String with pytest.raises(TypeError) as excinfo: BadUserSchema().dump(user) assert 'must be declared as a Field instance' in str(excinfo) @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.data) == 2 assert s.data[0] == SchemaClass().dump(users[0]).data def test_serializing_empty_list_returns_empty_list(): assert UserSchema(many=True).dump([]).data == [] assert UserMetaSchema(many=True).dump([]).data == [] def test_serializing_dict(): user = {"name": "foo", "email": "foo@bar.com", "age": 'badage', "various_data": {"foo": "bar"}} s = UserSchema().dump(user) assert s.data['name'] == "foo" assert 'age' in s.errors assert 'age' not in s.data assert s.data['various_data'] == {"foo": "bar"} def test_serializing_dict_with_meta_fields(): class MySchema(Schema): class Meta: fields = ('foo', 'bar') sch = MySchema() data, errors = sch.dump({'foo': 42, 'bar': 24, 'baz': 424}) assert not errors 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.data assert 'age' not in s.data assert 'name' in s.data @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.data assert 'name' in s.data assert 'age' in s.data def test_invalid_only_param(user): with pytest.raises(AttributeError): UserSchema(only=("_invalid", "name")).dump(user) def test_can_serialize_uuid(serialized_user, user): assert serialized_user.data['uid'] == str(user.uid) def test_can_serialize_time(user, serialized_user): expected = user.time_registered.isoformat()[:15] assert serialized_user.data['time_registered'] == expected def test_invalid_time(): u = User('Joe', time_registered='foo') s = UserSchema().dump(u) assert '"foo" cannot be formatted as a time.' in s.errors['time_registered'] def test_invalid_date(): u = User("Joe", birthdate='foo') s = UserSchema().dump(u) assert '"foo" cannot be formatted as a date.' in s.errors['birthdate'] def test_invalid_email(): u = User('Joe', email='bademail') s = UserSchema().dump(u) assert 'email' in s.errors assert 'Not a valid email address.' in s.errors['email'][0] def test_invalid_url(): u = User('Joe', homepage='badurl') s = UserSchema().dump(u) assert 'homepage' in s.errors assert 'Not a valid URL.' in s.errors['homepage'][0] def test_invalid_dict_but_okay(): u = User('Joe', various_data='baddict') s = UserSchema().dump(u) assert 'various_data' not in s.errors def test_json_module_is_deprecated(): with pytest.warns(DeprecationWarning): class UserJSONSchema(Schema): name = fields.String() class Meta: json_module = mockjson user = User('Joe') s = UserJSONSchema() result, errors = 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, errors = 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() data, errors = s.load(u) assert "Bad balance." in errors['balance'] assert "Bad homepage." in errors['homepage'] assert "Invalid email" in errors['email'] def test_load_errors_with_many(): class ErrorSchema(Schema): email = fields.Email() data = [ {'email': 'bademail'}, {'email': 'goo@email.com'}, {'email': 'anotherbademail'}, ] data, errors = ErrorSchema(many=True).load(data) 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(object): # 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.Field(required=True) bar = fields.Field() class Meta: only = ('bar', ) class ParentSchema(Schema): child = fields.Nested(ChildSchema, many=True, exclude=('foo',)) sch = ParentSchema(strict=True) obj = dict(child=CustomSet()) sch.dumps(obj) data = dict(child=[{'bar': 1}]) result = sch.load(data, partial=True) assert not result.errors def test_nested_only(): class ChildSchema(Schema): foo = fields.Field() bar = fields.Field() baz = fields.Field() class ParentSchema(Schema): bla = fields.Field() bli = fields.Field() 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.data assert 'blubb' in result.data assert 'bli' not in result.data child = result.data['blubb'] assert 'foo' in child assert 'bar' in child assert 'baz' not in child def test_nested_exclude(): class ChildSchema(Schema): foo = fields.Field() bar = fields.Field() baz = fields.Field() class ParentSchema(Schema): bla = fields.Field() bli = fields.Field() 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.data assert 'blubb' in result.data assert 'bli' not in result.data child = result.data['blubb'] assert 'foo' in child assert 'bar' in child assert 'baz' not in child def test_nested_only_and_exclude_with_dot_notation(): class ChildSchema(Schema): foo = fields.Field() bar = fields.Field() baz = fields.Field() class ParentSchema(Schema): bla = fields.Field() bli = fields.Field() 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.data assert 'blubb' in result.data assert 'bli' not in result.data child = result.data['blubb'] assert 'foo' not in child assert 'bar' in child assert 'baz' not in child def test_meta_nested_exclude(): class ChildSchema(Schema): foo = fields.Field() bar = fields.Field() baz = fields.Field() class ParentSchema(Schema): bla = fields.Field() bli = fields.Field() blubb = fields.Nested(ChildSchema) class Meta: exclude = ('blubb.foo',) sch = ParentSchema() data = dict(bla=1, bli=2, blubb=dict(foo=42, bar=24, baz=242)) result = sch.dump(data) assert 'bla' in result.data assert 'blubb' in result.data assert 'bli' in result.data child = result.data['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(object): """ 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(object): """ Implements an object with some attribute """ def __init__(self, attribute): """ :param str 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) assert not ParentSchema().dump(parent).errors def test_deeply_nested_only_and_exclude(): class GrandChildSchema(Schema): goo = fields.Field() gah = fields.Field() bah = fields.Field() class ChildSchema(Schema): foo = fields.Field() bar = fields.Field() flubb = fields.Nested(GrandChildSchema) class ParentSchema(Schema): bla = fields.Field() bli = fields.Field() 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.data assert 'blubb' in result.data assert 'bli' not in result.data child = result.data['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 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) class ParentSchema(Schema): str_dump_only = fields.String() str_load_only = fields.String() str_regular = fields.String() child = fields.Nested(ChildSchema) 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 not result.errors assert 'str_load_only' not in result.data assert 'str_dump_only' in result.data assert 'str_regular' in result.data child = result.data['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) assert not result.errors assert 'str_dump_only' not in result.data assert 'str_load_only' in result.data assert 'str_regular' in result.data child = result.data['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)) 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 not result.errors assert 'str_load_only' not in result.data assert 'str_dump_only' in result.data assert 'str_regular' in result.data child = result.data['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) assert not result.errors assert 'str_dump_only' not in result.data assert 'str_load_only' in result.data assert 'str_regular' in result.data child = result.data['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.Field() gah = fields.Field() bah = fields.Field() class ChildSchema(Schema): foo = fields.Field() bar = fields.Field() flubb = fields.Nested(GrandChildSchema) class ParentSchema(Schema): bla = fields.Field() bli = fields.Field() 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.data assert 'blubb' in result.data assert 'bli' not in result.data child = result.data['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.Field() bar = fields.Field() baz = fields.Field() sch = MySchema(only=('foo', 'bar'), exclude=('bar', )) data = dict(foo=42, bar=24, baz=242) result = sch.dump(data) assert 'foo' in result.data assert 'bar' not in result.data def test_exclude_invalid_attribute(): class MySchema(Schema): foo = fields.Field() sch = MySchema(exclude=('bar', )) assert sch.dump({'foo': 42}).data == {'foo': 42} def test_only_with_invalid_attribute(): class MySchema(Schema): foo = fields.Field() sch = MySchema(only=('bar', )) with pytest.raises(KeyError) as excinfo: sch.dump(dict(foo=42)) assert '"bar" is not a valid field' in str(excinfo.value.args[0]) def test_only_bounded_by_fields(): class MySchema(Schema): class Meta: fields = ('foo', ) sch = MySchema(only=('baz', )) assert sch.dump({'foo': 42}).data == {} def test_nested_only_and_exclude(): class Inner(Schema): foo = fields.Field() bar = fields.Field() baz = fields.Field() class Outer(Schema): inner = fields.Nested(Inner, only=('foo', 'bar'), exclude=('bar', )) sch = Outer() data = dict(inner=dict(foo=42, bar=24, baz=242)) result = sch.dump(data) assert 'foo' in result.data['inner'] assert 'bar' not in result.data['inner'] def test_nested_with_sets(): class Inner(Schema): foo = fields.Field() class Outer(Schema): inners = fields.Nested(Inner, many=True) sch = Outer() DataClass = namedtuple('DataClass', ['foo']) data = dict(inners=set([DataClass(42), DataClass(2)])) result = sch.dump(data) assert len(result.data['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 not result.errors assert result.data['name'] == u.name assert result.data['balance'] == decimal.Decimal('100.00') assert result.data['uppername'] == "JOHN" assert result.data['is_old'] is False assert result.data['created'] == utils.isoformat(u.created) assert result.data['updated_local'] == utils.isoformat(u.updated, localtime=True) assert result.data['finger_count'] == 10 assert result.data['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['name']) == fields.String assert type(s.fields['created']) == fields.DateTime assert type(s.fields['updated']) == fields.DateTime assert type(s.fields['updated_local']) == fields.LocalDateTime assert type(s.fields['age']) == fields.Float assert type(s.fields['balance']) == fields.Decimal assert type(s.fields['registered']) == fields.Boolean assert type(s.fields['sex_choices']) == fields.Raw assert type(s.fields['hair_colors']) == fields.Raw assert type(s.fields['finger_count']) == fields.Integer assert type(s.fields['uid']) == fields.UUID assert type(s.fields['time_registered']) == fields.Time assert type(s.fields['birthdate']) == fields.Date assert type(s.fields['since_created']) == fields.TimeDelta def test_meta_field_not_on_obj_raises_attribute_error(user): class BadUserSchema(Schema): class Meta: fields = ('name', 'notfound') with pytest.raises(AttributeError): BadUserSchema().dump(user) def test_exclude_fields(user): s = UserExcludeSchema().dump(user) assert "created" not in s.data assert "updated" not in s.data assert "name" in s.data def test_fields_option_must_be_list_or_tuple(user): with pytest.raises(ValueError): class BadFields(Schema): class Meta: fields = "name" def test_exclude_option_must_be_list_or_tuple(user): with pytest.raises(ValueError): class BadExclude(Schema): class Meta: exclude = "name" def test_dateformat_option(user): fmt = '%Y-%m' class DateFormatSchema(Schema): updated = fields.DateTime("%m-%d") class Meta: fields = ('created', 'updated') dateformat = fmt serialized = DateFormatSchema().dump(user) assert serialized.data['created'] == user.created.strftime(fmt) assert serialized.data['updated'] == user.updated.strftime("%m-%d") 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.data['created'] == utils.isoformat(user.created) assert serialized.data['updated'] == user.updated.strftime("%m-%d") def test_inherit_meta(user): class InheritedMetaSchema(UserMetaSchema): pass result = InheritedMetaSchema().dump(user).data expected = UserMetaSchema().dump(user).data assert result == expected def test_inherit_meta_override(): class Parent(Schema): class Meta: strict = True fields = ('name', 'email') class Child(Schema): class Meta(Parent.Meta): strict = False child = Child() assert child.opts.fields == ('name', 'email') assert child.opts.strict is False def test_additional(user): s = UserAdditionalSchema().dump(user) assert s.data['lowername'] == user.name.lower() assert s.data['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.data == {} assert s.errors == {} class CustomError(Exception): pass class MySchema(Schema): name = fields.String() email = fields.Email() age = fields.Integer() def handle_error(self, errors, obj): raise CustomError('Something bad happened') class TestHandleError: def test_dump_with_custom_error_handler(self, user): user.age = 'notavalidage' with pytest.raises(CustomError): MySchema().dump(user) def test_dump_with_custom_error_handler_and_strict(self, user): user.age = 'notavalidage' with pytest.raises(CustomError): MySchema(strict=True).dump(user) def test_load_with_custom_error_handler(self): in_data = {'email': 'invalid'} class MySchema3(Schema): email = fields.Email() def handle_error(self, error, data): assert type(error) is ValidationError assert 'email' in error.messages assert error.field_names == ['email'] assert error.fields == [self.fields['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): assert type(error) is ValidationError assert 'email' in error.messages assert error.field_names == ['email'] assert error.fields == [self.fields['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): assert type(error) is ValidationError assert 'num' in error.messages assert error.field_names == ['num'] assert error.fields == [self.fields['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): raise ValidationError('Invalid schema!') def handle_error(self, error, data): assert type(error) is ValidationError assert '_schema' in error.messages assert error.field_names == ['_schema'] assert error.fields == [] 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() _, errors = schema.load([ {'foo': 'bar'}, {'foo': 'baz'} ], many=True) assert len(errors[0]['foo']) == 1 assert len(errors[1]['foo']) == 1 _, errors2 = schema.load({'foo': 'bar'}) assert len(errors2['foo']) == 1 def test_raises_error_with_list(self): def validator(val): raise ValidationError(['err1', 'err2']) class MySchema(Schema): foo = fields.Field(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.Field(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.Field() b = fields.Field() @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, strict=True) rep = repr(ser) assert 'MySchema' in rep assert 'strict=True' 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) blog = Blog("Monty's blog", user=user, categories=["humor", "violence"], collaborators=[col1, col2]) return blog def test_flat_nested(self, blog): class FlatBlogSchema(Schema): name = fields.String() user = fields.Nested(UserSchema, only='name') collaborators = fields.Nested(UserSchema, only='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 # 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.data def test_nested_with_attribute_none(self): class InnerSchema(Schema): bar = fields.Field() class MySchema(Schema): foo = fields.Nested(InnerSchema) class MySchema2(Schema): foo = fields.Nested(InnerSchema) s = MySchema() result = s.dump({'foo': None}) assert result.data['foo'] is None s2 = MySchema2() result2 = s2.dump({'foo': None}) assert result2.data['foo'] is None def test_flat_nested2(self, blog): class FlatBlogSchema(Schema): name = fields.String() collaborators = fields.Nested(UserSchema, many=True, only='uid') s = FlatBlogSchema() data, _ = s.dump(blog) assert data['collaborators'][0] == str(blog.collaborators[0].uid) def test_nested_field_does_not_validate_required(self): class BlogRequiredSchema(Schema): user = fields.Nested(UserSchema, required=True) b = Blog('Authorless blog', user=None) _, errs = BlogRequiredSchema().dump(b) assert 'user' not in errs def test_nested_required_errors_with_load_from(self): class DatesInfoSchema(Schema): created = fields.DateTime(required=True) updated = fields.DateTime(required=True) class UserSimpleSchema(Schema): name = fields.String(required=True) time_registered = fields.Time(load_from='timeRegistered', required=True) dates_info = fields.Nested(DatesInfoSchema, load_from='datesInfo', required=True) class BlogRequiredSchema(Schema): user = fields.Nested(UserSimpleSchema, required=True) _, errs = BlogRequiredSchema().load({}) assert 'timeRegistered' in errs['user'] assert 'datesInfo' in errs['user'] def test_nested_none(self): class BlogDefaultSchema(Schema): user = fields.Nested(UserSchema, 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 def test_nested_many_fields(self, blog): serialized_blog, _ = BlogSchema().dump(blog) expected = [UserSchema().dump(col)[0] for col in blog.collaborators] assert serialized_blog['collaborators'] == expected def test_nested_meta_many(self, blog): serialized_blog = BlogUserMetaSchema().dump(blog)[0] assert len(serialized_blog['collaborators']) == 2 expected = [UserMetaSchema().dump(col)[0] 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)[0] assert serialized_blog['collaborators'] == [{"id": col1.id}, {"id": col2.id}] def test_exclude(self, blog): serialized = BlogSchemaExclude().dump(blog)[0] assert "uppername" not in serialized['user'].keys() def test_list_field(self, blog): serialized = BlogSchema().dump(blog)[0] 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, errors = BlogSchema().load(in_data) collabs = data['collaborators'] assert len(collabs) == 2 assert all(type(each) == User for each in collabs) assert collabs[0].name == in_data['collaborators'][0]['name'] def test_nested_errors(self): _, errors = BlogSchema().load( {'title': "Monty's blog", 'user': {'name': 'Monty', 'email': 'foo'}} ) 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_strict(self): with pytest.raises(ValidationError) as excinfo: _, errors = BlogSchema(strict=True).load( {'title': "Monty's blog", 'user': {'name': 'Monty', 'email': 'foo'}} ) assert 'email' in str(excinfo) def test_nested_dump_errors(self, blog): blog.user.email = "foo" _, errors = BlogSchema().dump(blog) 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_dump_strict(self, blog): blog.user.email = "foo" with pytest.raises(ValidationError) as excinfo: _, errors = BlogSchema(strict=True).dump(blog) assert 'email' in str(excinfo) def test_nested_method_field(self, blog): data = BlogSchema().dump(blog)[0] assert data['user']['is_old'] assert data['collaborators'][0]['is_old'] def test_nested_function_field(self, blog, user): data = BlogSchema().dump(blog)[0] assert data['user']['lowername'] == user.name.lower() expected = blog.collaborators[0].name.lower() assert data['collaborators'][0]['lowername'] == expected def test_nested_prefixed_field(self, blog, user): data = BlogSchemaPrefixedUser().dump(blog)[0] assert data['user']['usr_name'] == user.name assert data['user']['usr_lowername'] == user.name.lower() def test_nested_prefixed_many_field(self, blog): data = BlogSchemaPrefixedUser().dump(blog)[0] assert data['collaborators'][0]['usr_name'] == blog.collaborators[0].name def test_invalid_float_field(self): user = User("Joe", age="1b2") _, errors = UserSchema().dump(user) assert "age" in errors def test_serializer_meta_with_nested_fields(self, blog, user): data = BlogSchemaMeta().dump(blog)[0] assert data['title'] == blog.title assert data['user'] == UserSchema().dump(user).data assert data['collaborators'] == [UserSchema().dump(c).data 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.data['user'] == UserMetaSchema().dump(blog.user).data 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.Field() class MySchema(Schema): inner = fields.Nested(InnerSchema, many=True) sch = MySchema() result = sch.load({'inner': [{'foo': 42}]}) assert not result.errors result = sch.load({'inner': 'invalid'}) assert 'inner' in result.errors assert result.errors['inner'] == ['Invalid type.'] class OuterSchema(Schema): inner = fields.Nested(InnerSchema) schema = OuterSchema() _, errors = schema.load({'inner': 1}) 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.Field(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() _, errors = outer.load({'inner': [{}]}) assert 'inner' in errors assert '_field' in errors['inner'] def test_missing_required_nested_field(self): class Inner(Schema): inner_req = fields.Field(required=True, error_messages={'required': 'Oops'}) inner_not_req = fields.Field() inner_bad = fields.Integer(required=True, error_messages={'required': 'Int plz'}) class Middle(Schema): middle_many_req = fields.Nested(Inner, required=True, many=True) middle_req_2 = fields.Nested(Inner, required=True) middle_not_req = fields.Nested(Inner) middle_field = fields.Field(required=True, error_messages={'required': 'middlin'}) class Outer(Schema): outer_req = fields.Nested(Middle, required=True) outer_many_req = fields.Nested(Middle, required=True, many=True) outer_not_req = fields.Nested(Middle) outer_many_not_req = fields.Nested(Middle, many=True) outer = Outer() expected = { 'outer_many_req': {0: {'middle_many_req': {0: {'inner_bad': ['Int plz'], 'inner_req': ['Oops']}}, 'middle_req_2': {'inner_bad': ['Int plz'], 'inner_req': ['Oops']}, 'middle_field': ['middlin']}}, 'outer_req': {'middle_field': ['middlin'], 'middle_many_req': {0: {'inner_bad': ['Int plz'], 'inner_req': ['Oops']}}, 'middle_req_2': {'inner_bad': ['Int plz'], 'inner_req': ['Oops']}}} data, errors = outer.load({}) assert errors == expected 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_within_itself(self, user, employer): class SelfSchema(Schema): name = fields.String() age = fields.Integer() employer = fields.Nested('self', exclude=('employer', )) data, errors = SelfSchema().dump(user) assert not errors 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, errors = SelfReferencingSchema().dump(user) assert not errors 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("self", exclude=('employer', )) class Meta: additional = ('name', 'age') data, errors = SelfSchema().dump(user) assert not errors assert data['name'] == user.name assert data['age'] == user.age assert data['employer']['name'] == employer.name assert data['employer']['age'] == employer.age def test_recursive_missing_required_field(self): class BasicSchema(Schema): sub_basics = fields.Nested("self", required=True) data, errors = BasicSchema().load({}) assert data == {} assert errors == { 'sub_basics': ['Missing data for required field.'] } def test_recursive_missing_required_field_one_level_in(self): class BasicSchema(Schema): sub_basics = fields.Nested("self", required=True, exclude=('sub_basics', )) simple_field = fields.Str(required=True) class DeepSchema(Schema): basic = fields.Nested(BasicSchema(), required=True) data, errors = DeepSchema().load({}) assert data == {} assert errors == { 'basic': { 'sub_basics': [u'Missing data for required field.'], 'simple_field': [u'Missing data for required field.'], } } partially_valid = { 'basic': {'sub_basics': {'simple_field': 'foo'}} } data, errors = DeepSchema().load(partially_valid) assert data == partially_valid assert errors == { 'basic': { 'simple_field': [u'Missing data for required field.'], } } partially_valid2 = { 'basic': {'simple_field': 'foo'} } data, errors = DeepSchema().load(partially_valid2) assert data == partially_valid2 assert errors == { 'basic': { 'sub_basics': ['Missing data for required field.'], } } def test_nested_self_with_only_param(self, user, employer): class SelfSchema(Schema): employer = fields.Nested('self', only=('name', )) class Meta: fields = ('name', 'employer') data = SelfSchema().dump(user)[0] assert data['name'] == user.name assert data['employer']['name'] == employer.name assert 'age' not in data['employer'] def test_multiple_nested_self_fields(self, user): class MultipleSelfSchema(Schema): emp = fields.Nested('self', only='name', attribute='employer') rels = fields.Nested('self', only='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, errors = schema.dump(user) assert not errors assert len(data['rels']) == len(user.relatives) relative = data['rels'][0] assert relative == user.relatives[0].name def test_nested_many(self): 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)[0] 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.Field(required=True) def test_serialization_with_required_field(): user = User(name=None) data, errors = RequiredUserSchema().dump(user) # Does not validate required assert 'name' not in errors def test_deserialization_with_required_field(): in_data = {} data, errors = RequiredUserSchema().load(in_data) 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"}) data, errors = ValidatingSchema().load({'name': 'foo'}) assert errors assert 'color' in errors assert "Missing data for required field." in errors['color'] _, errors = ValidatingSchema().load({'color': 'green'}) 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)[0] assert data['is_owner'] is True nonowner = User('Fred') data = serializer.dump(nonowner)[0] 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)[0] assert data['is_collab'] is True noncollab = User('Foo') data = serializer.dump(noncollab)[0] 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(strict=True) # no context serializer.context = None with pytest.raises(ValidationError) as excinfo: serializer.dump(owner) msg = 'No context available for Function field {0!r}'.format('is_collab') assert msg in str(excinfo) def test_function_field_handles_bound_serializer(self): class SerializeA(object): 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(strict=True) # no context serializer.context = None data = serializer.dump(owner)[0] assert data['is_collab'] is '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(strict=True) ser.context['info'] = 'i like bikes' obj = { 'inner': {} } result = ser.dump(obj) assert result.data['inner']['likes_bikes'] is True 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.data['author_name'] == blog.user.name class TestFieldInheritance: def test_inherit_fields_from_schema_subclass(self): expected = OrderedDict([ ('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): expected = OrderedDict([ ('field_a', fields.Number()), ('field_b', fields.Number()), ]) class PlainBaseClass(object): 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 = OrderedDict([ ('field_a', fields.String()), ('field_c', fields.String()), ('field_b', 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.data['name'] == user_dict['_name'] assert result.data['email'] == user_dict['_email'] assert not result.errors # 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.data, user_dicts): assert result['name'] == user_dict['_name'] assert result['email'] == user_dict['_email'] assert not results.errors # 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 'allow_none_field' not in errors data['allow_none_required_field'] = None errors = string_schema.validate(data) assert 'allow_none_required_field' not in errors 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.Field(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, default=42) str_with_default = fields.Str(allow_none=True, 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.data # the rest of the keys are in the result.data assert all(k in result.data for k in d.keys()) def test_none_is_serialized_to_none(self, schema, data): assert schema.validate(data) == {} result = schema.dump(data) for key in data.keys(): msg = 'result.data[{0!r}] should be None'.format(key) assert result.data[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.data['int_with_default'] == 42 assert result.data['str_with_default'] == 'foo' def test_loading_none(self, schema, data): result = schema.load(data) assert not result.errors for key in data.keys(): result.data[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.data # the rest of the keys are in the result.data assert all(k in result.data for k in d.keys()) 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 not result.errors assert 'str_load_only' not in result.data assert 'str_dump_only' in result.data assert 'str_regular' in result.data def test_dump_only(self, schema, data): result = schema.load(data) assert not result.errors assert 'str_dump_only' not in result.data assert 'str_load_only' in result.data assert 'str_regular' in result.data class TestStrictDefault: class SchemaTrueByMeta(Schema): class Meta: strict = True class SchemaFalseByMeta(Schema): class Meta: strict = False class SchemaWithoutMeta(Schema): pass def test_default(self): assert self.SchemaWithoutMeta().strict is False def test_meta_true(self): assert self.SchemaTrueByMeta().strict is True def test_meta_false(self): assert self.SchemaFalseByMeta().strict is False def test_default_init_true(self): assert self.SchemaWithoutMeta(strict=True).strict is True def test_default_init_false(self): assert self.SchemaWithoutMeta(strict=False).strict is False def test_meta_true_init_true(self): assert self.SchemaTrueByMeta(strict=True).strict is True def test_meta_true_init_false(self): assert self.SchemaTrueByMeta(strict=False).strict is False def test_meta_false_init_true(self): assert self.SchemaFalseByMeta(strict=True).strict is True def test_meta_false_init_false(self): assert self.SchemaFalseByMeta(strict=False).strict is False marshmallow-3.0.0b3/tests/test_serialization.py000066400000000000000000000731411314633611600217270ustar00rootroot00000000000000# -*- coding: utf-8 -*- """Tests for field serialization.""" from collections import namedtuple, OrderedDict import datetime as dt import itertools import decimal import uuid import pytest from marshmallow import Schema, fields, utils from marshmallow.exceptions import ValidationError from marshmallow.compat import basestring from marshmallow.utils import missing as missing_ from tests.base import User, ALL_FIELDS class DateTimeList: def __init__(self, dtimes): self.dtimes = dtimes class IntegerList: def __init__(self, ints): self.ints = ints class TestFieldSerialization: @pytest.fixture def user(self): return User("Foo", email="foo@bar.com", age=42) def test_default(self, user): field = fields.Field(default='nan') assert field.serialize('age', {}) == 'nan' @pytest.mark.parametrize(('value', 'expected'), [ (42, float(42)), (0, float(0)), (None, None), ]) def test_number(self, value, expected, user): field = fields.Number() user.age = value assert field.serialize('age', user) == expected def test_number_as_string(self, user): user.age = 42 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 field = fields.Number(as_string=True, allow_none=True) assert field.serialize('age', user) is None def test_callable_default(self, user): field = fields.Field(default=lambda: 'nan') assert field.serialize('age', {}) == 'nan' def test_function_field_passed_func(self, user): field = fields.Function(lambda obj: obj.name.upper()) assert "FOO" == field.serialize("key", user) 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 "FOO" == field.serialize("key", user) # 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_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'] ) field.parent = Parent(context={'key': 'BAR'}) assert "FOOBAR" == field.serialize("key", user) def test_function_field_passed_uncallable_object(self): with pytest.raises(ValueError): 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(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(default=None) assert field.serialize('age', user) is None def test_uuid_field(self, user): user.uuid1 = '{12345678-1234-5678-1234-567812345678}' user.uuid2 = uuid.UUID('12345678123456781234567812345678') user.uuid3 = None user.uuid4 = 'this is not a UUID' user.uuid5 = 42 user.uuid6 = {} field = fields.UUID() assert isinstance(field.serialize('uuid1', user), str) assert field.serialize('uuid1', user) == '12345678-1234-5678-1234-567812345678' assert isinstance(field.serialize('uuid2', user), str) assert field.serialize('uuid2', user) == '12345678-1234-5678-1234-567812345678' assert field.serialize('uuid3', user) is None with pytest.raises(ValidationError): field.serialize('uuid4', user) with pytest.raises(ValidationError): field.serialize('uuid5', user) with pytest.raises(ValidationError): field.serialize('uuid6', user) def test_decimal_field(self, user): user.m1 = 12 user.m2 = '12.355' user.m3 = decimal.Decimal(1) user.m4 = None user.m5 = 'abc' user.m6 = [1, 2] 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 with pytest.raises(ValidationError): field.serialize('m5', user) with pytest.raises(ValidationError): field.serialize('m6', user) 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 with pytest.raises(ValidationError): field.serialize('m5', user) with pytest.raises(ValidationError): field.serialize('m6', user) 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 with pytest.raises(ValidationError): field.serialize('m5', user) with pytest.raises(ValidationError): field.serialize('m6', user) def test_decimal_field_string(self, user): user.m1 = 12 user.m2 = '12.355' user.m3 = decimal.Decimal(1) user.m4 = None user.m5 = 'abc' user.m6 = [1, 2] field = fields.Decimal(as_string=True) assert isinstance(field.serialize('m1', user), basestring) assert field.serialize('m1', user) == '12' assert isinstance(field.serialize('m2', user), basestring) assert field.serialize('m2', user) == '12.355' assert isinstance(field.serialize('m3', user), basestring) assert field.serialize('m3', user) == '1' assert field.serialize('m4', user) is None with pytest.raises(ValidationError): field.serialize('m5', user) with pytest.raises(ValidationError): field.serialize('m6', user) field = fields.Decimal(1, as_string=True) assert isinstance(field.serialize('m1', user), basestring) assert field.serialize('m1', user) == '12.0' assert isinstance(field.serialize('m2', user), basestring) assert field.serialize('m2', user) == '12.4' assert isinstance(field.serialize('m3', user), basestring) assert field.serialize('m3', user) == '1.0' assert field.serialize('m4', user) is None with pytest.raises(ValidationError): field.serialize('m5', user) with pytest.raises(ValidationError): field.serialize('m6', user) field = fields.Decimal(1, decimal.ROUND_DOWN, as_string=True) assert isinstance(field.serialize('m1', user), basestring) assert field.serialize('m1', user) == '12.0' assert isinstance(field.serialize('m2', user), basestring) assert field.serialize('m2', user) == '12.3' assert isinstance(field.serialize('m3', user), basestring) assert field.serialize('m3', user) == '1.0' assert field.serialize('m4', user) is None with pytest.raises(ValidationError): field.serialize('m5', user) with pytest.raises(ValidationError): field.serialize('m6', user) 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() and not m1s.is_signed() m2s = field.serialize('m2', user) assert isinstance(m2s, decimal.Decimal) assert m2s.is_qnan() and not m2s.is_signed() m3s = field.serialize('m3', user) assert isinstance(m3s, decimal.Decimal) assert m3s.is_qnan() and not m3s.is_signed() m4s = field.serialize('m4', user) assert isinstance(m4s, decimal.Decimal) assert m4s.is_qnan() and not m4s.is_signed() m5s = field.serialize('m5', user) assert isinstance(m5s, decimal.Decimal) assert m5s.is_infinite() and m5s.is_signed() m6s = field.serialize('m6', user) assert isinstance(m6s, decimal.Decimal) assert m6s.is_infinite() and not m6s.is_signed() m7s = field.serialize('m7', user) assert isinstance(m7s, decimal.Decimal) assert m7s.is_zero() and m7s.is_signed() field = fields.Decimal(as_string=True, allow_nan=True) m2s = field.serialize('m2', user) assert isinstance(m2s, basestring) assert m2s == user.m2 m5s = field.serialize('m5', user) assert isinstance(m5s, basestring) assert m5s == user.m5 m6s = field.serialize('m6', user) assert isinstance(m6s, basestring) assert m6s == user.m6 def test_decimal_field_special_values_not_permitted(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) with pytest.raises(ValidationError): field.serialize('m1', user) with pytest.raises(ValidationError): field.serialize('m2', user) with pytest.raises(ValidationError): field.serialize('m3', user) with pytest.raises(ValidationError): field.serialize('m4', user) with pytest.raises(ValidationError): field.serialize('m5', user) with pytest.raises(ValidationError): field.serialize('m6', user) m7s = field.serialize('m7', user) assert isinstance(m7s, decimal.Decimal) assert m7s.is_zero() and 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, basestring) assert s == user.m1 field = fields.Decimal(as_string=True, places=2) s = field.serialize('m1', user) assert isinstance(s, basestring) 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_function_with_uncallable_param(self): with pytest.raises(ValueError): fields.Function("uncallable") def test_email_field_validates(self, user): user.email = 'bademail' field = fields.Email() with pytest.raises(ValidationError): field.serialize('email', user) 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_invalid_dict_but_okay(self, user): user.various_data = 'okaydict' field = fields.Dict() field.serialize('various_data', user) assert field.serialize('various_data', user) == 'okaydict' def test_dict_field_serialize(self, user): user.various_data = {"foo": "bar"} field = fields.Dict() assert field.serialize('various_data', user) == {"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_url_field_serialize_none(self, user): user.homepage = None field = fields.Url() assert field.serialize('homepage', user) is None def test_url_field_validates(self, user): user.homepage = 'badhomepage' field = fields.URL() with pytest.raises(ValidationError): field.serialize('homepage', user) def test_method_field_with_method_missing(self): class BadSerializer(Schema): bad_field = fields.Method('invalid') u = User('Foo') with pytest.raises(ValueError): BadSerializer().dump(u) 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') u = User('Foo') with pytest.raises(ValueError): BadSerializer().dump(u) # 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_dump_to_param(self): class DumpToSchema(Schema): name = fields.String(dump_to='NamE') years = fields.Integer(dump_to='YearS') data = { 'name': 'Richard', 'years': 11 } result, errors = DumpToSchema().dump(data) assert result == { 'NamE': 'Richard', 'YearS': 11 } def test_serialize_with_attribute_and_dump_to_uses_dump_to(self): class ConfusedDumpToAndAttributeSerializer(Schema): name = fields.String(dump_to="FullName") username = fields.String(attribute='uname', dump_to='UserName') years = fields.Integer(attribute='le_wild_age', dump_to='Years') data = { 'name': 'Mick', 'uname': 'mick_the_awesome', 'le_wild_age': 999 } result, errors = ConfusedDumpToAndAttributeSerializer().dump(data) assert result == { 'FullName': 'Mick', 'UserName': 'mick_the_awesome', 'Years': 999, } def test_datetime_serializes_to_iso_by_default(self, user): field = fields.DateTime() # No format specified expected = utils.isoformat(user.created, localtime=False) assert field.serialize('created', user) == expected @pytest.mark.parametrize('value', [ 'invalid', [], 24, ]) def test_datetime_invalid_serialization(self, value, user): field = fields.DateTime() user.created = value with pytest.raises(ValidationError) as excinfo: field.serialize('created', user) assert excinfo.value.args[0] == '"{0}" cannot be formatted as a datetime.'.format(value) @pytest.mark.parametrize('fmt', ['rfc', 'rfc822']) def test_datetime_field_rfc822(self, fmt, user): field = fields.DateTime(format=fmt) expected = utils.rfcformat(user.created, localtime=False) assert field.serialize("created", user) == expected def test_localdatetime_rfc_field(self, user): field = fields.LocalDateTime(format='rfc') expected = utils.rfcformat(user.created, localtime=True) assert field.serialize("created", user) == expected @pytest.mark.parametrize('fmt', ['iso', 'iso8601']) def test_datetime_iso8601(self, fmt, user): field = fields.DateTime(format=fmt) expected = utils.isoformat(user.created, localtime=False) assert field.serialize("created", user) == expected def test_localdatetime_iso(self, user): field = fields.LocalDateTime(format="iso") expected = utils.isoformat(user.created, localtime=True) assert field.serialize("created", user) == expected def test_datetime_format(self, user): format = "%Y-%m-%d" field = fields.DateTime(format=format) assert field.serialize("created", user) == user.created.strftime(format) 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_formattedstring_field(self): field = fields.FormattedString('Hello {name}') user = User(name='Monty') assert field.serialize('name', user) == 'Hello Monty' # Regression test for https://github.com/marshmallow-code/marshmallow/issues/348 def test_formattedstring_field_on_schema(self): class MySchema(Schema): greeting = fields.FormattedString('Hello {name}') user = User(name='Monty') assert MySchema().dump(user).data['greeting'] == 'Hello Monty' def test_string_field_default_to_empty_string(self, user): field = fields.String(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('in_data', [ 'badvalue', '', [], 42, ]) def test_invalid_time_field_serialization(self, in_data, user): field = fields.Time() user.time_registered = in_data with pytest.raises(ValidationError) as excinfo: field.serialize('time_registered', user) msg = '"{0}" cannot be formatted as a time.'.format(in_data) assert excinfo.value.args[0] == msg 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 @pytest.mark.parametrize('in_data', [ 'badvalue', '', [], 42, ]) def test_invalid_date_field_serialization(self, in_data, user): field = fields.Date() user.birthdate = in_data with pytest.raises(ValidationError) as excinfo: field.serialize('birthdate', user) msg = '"{0}" cannot be formatted as a date.'.format(in_data) assert excinfo.value.args[0] == msg 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) 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.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 user.d6 = None assert field.serialize('d6', user) is None def test_datetime_list_field(self): obj = DateTimeList([dt.datetime.utcnow(), dt.datetime.now()]) field = fields.List(fields.DateTime) result = field.serialize('dtimes', obj) assert all([type(each) == str for each in result]) def test_list_field_with_error(self): obj = DateTimeList(['invaliddate']) field = fields.List(fields.DateTime) with pytest.raises(ValidationError): field.serialize('dtimes', obj) def test_datetime_list_serialize_single_value(self): obj = DateTimeList(dt.datetime.utcnow()) field = fields.List(fields.DateTime) result = field.serialize('dtimes', obj) assert len(result) == 1 assert type(result[0]) == str 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_respect_inner_attribute(self): now = dt.datetime.now() obj = DateTimeList([now]) field = fields.List(fields.Int(attribute='day')) assert field.serialize('dtimes', obj) == [now.day] def test_list_field_respect_inner_attribute_single_value(self): now = dt.datetime.now() obj = DateTimeList(now) field = fields.List(fields.Int(attribute='day')) assert field.serialize('dtimes', obj) == [now.day] def test_list_field_work_with_generator_single_value(self): def custom_generator(): yield dt.datetime.utcnow() 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(): for dtime in [dt.datetime.utcnow(), dt.datetime.now()]: yield dtime 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_error(self): def custom_generator(): for dtime in [dt.datetime.utcnow(), "m", dt.datetime.now()]: yield dtime obj = DateTimeList(custom_generator()) field = fields.List(fields.DateTime) with pytest.raises(ValidationError): field.serialize('dtimes', obj) def test_list_field_work_with_generators_empty_generator_returns_none_for_every_non_returning_yield_statement(self): # noqa 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 = 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") with pytest.raises(ValueError) as excinfo: fields.List(ASchema) expected_msg = ('The type of the list elements must be a subclass ' 'of marshmallow.base.FieldABC') assert expected_msg in str(excinfo) def test_serialize_does_not_apply_validators(self, user): field = fields.Field(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}).data['foo'] == 42 assert sch.dump({'foo': 24}).data['foo'] == 42 def test_constant_field_serialize_when_omitted(self): class MiniUserSchema(Schema): name = fields.Constant('bill') s = MiniUserSchema() assert s.dump({}).data['name'] == 'bill' @pytest.mark.parametrize('FieldClass', ALL_FIELDS) def test_all_fields_serialize_none_to_none(self, FieldClass): if FieldClass == fields.FormattedString: field = FieldClass('{foo}', allow_none=True) else: field = FieldClass(allow_none=True) res = field.serialize('foo', {'foo': None}) if FieldClass == fields.FormattedString: assert res == 'None' else: assert res is None def test_serializing_named_tuple(): Point = namedtuple('Point', ['x', 'y']) field = fields.Field() p = Point(x=4, y=2) assert field.serialize('x', p) == 4 def test_serializing_named_tuple_with_meta(): Point = namedtuple('Point', ['x', 'y']) p = Point(x=4, y=2) class PointSerializer(Schema): class Meta: fields = ('x', 'y') serialized = PointSerializer().dump(p) assert serialized.data['x'] == 4 assert serialized.data['y'] == 2 def test_serializing_slice(): values = [{'value': value} for value in range(5)] slice = itertools.islice(values, None) class ValueSchema(Schema): value = fields.Int() serialized = ValueSchema(many=True).dump(slice).data assert serialized == values marshmallow-3.0.0b3/tests/test_utils.py000066400000000000000000000162341314633611600202120ustar00rootroot00000000000000# -*- coding: utf-8 -*- import datetime as dt from collections import namedtuple from functools import partial import pytest from marshmallow import utils from tests.base import ( assert_datetime_equal, central, assert_time_equal, assert_date_equal, ) def test_to_marshallable_type(): class Foo(object): CLASS_VAR = 'bar' def __init__(self): self.attribute = 'baz' @property def prop(self): return 42 obj = Foo() u_dict = utils.to_marshallable_type(obj) assert u_dict['CLASS_VAR'] == Foo.CLASS_VAR assert u_dict['attribute'] == obj.attribute assert u_dict['prop'] == obj.prop def test_to_marshallable_type_none(): assert utils.to_marshallable_type(None) is None PointNT = namedtuple('Point', ['x', 'y']) def test_to_marshallable_type_with_namedtuple(): p = PointNT(24, 42) result = utils.to_marshallable_type(p) assert result['x'] == p.x assert result['y'] == p.y class PointClass(object): def __init__(self, x, y): self.x = x self.y = y @pytest.mark.parametrize('obj', [ PointNT(24, 42), PointClass(24, 42), {'x': 24, 'y': 42} ]) def test_get_value_from_object(obj): result = utils.get_value(obj, 'x') assert result == 24 result2 = utils.get_value(obj, 'y') assert result2 == 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(object): 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(): l = [1, 2, 3] assert utils.get_value(l, 1) == 2 class MyInt(int): pass assert utils.get_value(l, 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(): Point = namedtuple('Point', ['x', 'y']) p = Point(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 l = [24, 42] assert utils.is_keyed_tuple(l) is False def test_to_marshallable_type_list(): assert utils.to_marshallable_type(['foo', 'bar']) == ['foo', 'bar'] def test_to_marshallable_type_generator(): gen = (e for e in ['foo', 'bar']) assert utils.to_marshallable_type(gen) == ['foo', 'bar'] def test_marshallable(): class ObjContainer(object): contained = {"foo": 1} def __marshallable__(self): return self.contained obj = ObjContainer() assert utils.to_marshallable_type(obj) == {"foo": 1} 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 def test_rfcformat_gmt_naive(): d = dt.datetime(2013, 11, 10, 1, 23, 45) assert utils.rfcformat(d) == "Sun, 10 Nov 2013 01:23:45 -0000" def test_rfcformat_central(): d = central.localize(dt.datetime(2013, 11, 10, 1, 23, 45), is_dst=False) assert utils.rfcformat(d) == 'Sun, 10 Nov 2013 07:23:45 -0000' def test_rfcformat_central_localized(): d = central.localize(dt.datetime(2013, 11, 10, 8, 23, 45), is_dst=False) assert utils.rfcformat(d, localtime=True) == "Sun, 10 Nov 2013 08:23:45 -0600" def test_isoformat(): d = dt.datetime(2013, 11, 10, 1, 23, 45) assert utils.isoformat(d) == '2013-11-10T01:23:45+00:00' def test_isoformat_tzaware(): d = central.localize(dt.datetime(2013, 11, 10, 1, 23, 45), is_dst=False) assert utils.isoformat(d) == "2013-11-10T07:23:45+00:00" def test_isoformat_localtime(): d = central.localize(dt.datetime(2013, 11, 10, 1, 23, 45), is_dst=False) assert utils.isoformat(d, localtime=True) == "2013-11-10T01:23:45-06:00" def test_from_datestring(): d = dt.datetime.now() rfc = utils.rfcformat(d) iso = d.isoformat() assert_date_equal(utils.from_datestring(rfc), d) assert_date_equal(utils.from_datestring(iso), d) @pytest.mark.parametrize('use_dateutil', [True, False]) def test_from_rfc(use_dateutil): d = dt.datetime.now() rfc = utils.rfcformat(d) result = utils.from_rfc(rfc, use_dateutil=use_dateutil) assert type(result) == dt.datetime assert_datetime_equal(result, d) @pytest.mark.parametrize('use_dateutil', [True, False]) def test_from_iso(use_dateutil): d = dt.datetime.now() formatted = d.isoformat() result = utils.from_iso(formatted, use_dateutil=use_dateutil) assert type(result) == dt.datetime assert_datetime_equal(result, d) def test_from_iso_with_tz(): d = central.localize(dt.datetime.now()) formatted = d.isoformat() result = utils.from_iso(formatted) assert_datetime_equal(result, d) if utils.dateutil_available: # Note a naive datetime assert result.tzinfo is not None # Test with and without dateutil @pytest.mark.parametrize('use_dateutil', [True, False]) def test_from_iso_time_with_microseconds(use_dateutil): t = dt.time(1, 23, 45, 6789) formatted = t.isoformat() result = utils.from_iso_time(formatted, use_dateutil=use_dateutil) assert type(result) == dt.time assert_time_equal(result, t, microseconds=True) @pytest.mark.parametrize('use_dateutil', [True, False]) def test_from_iso_time_without_microseconds(use_dateutil): t = dt.time(1, 23, 45) formatted = t.isoformat() result = utils.from_iso_time(formatted, use_dateutil=use_dateutil) assert type(result) == dt.time assert_time_equal(result, t, microseconds=True) @pytest.mark.parametrize('use_dateutil', [True, False]) def test_from_iso_date(use_dateutil): d = dt.date(2014, 8, 21) iso_date = d.isoformat() result = utils.from_iso_date(iso_date, use_dateutil=use_dateutil) assert type(result) == dt.date assert_date_equal(result, d) def test_get_func_args(): def f1(foo, bar): pass f2 = partial(f1, 'baz') class F3(object): def __call__(self, foo, bar): pass f3 = F3() for func in [f1, f2, f3]: assert utils.get_func_args(func) == ['foo', 'bar'] marshmallow-3.0.0b3/tests/test_validate.py000066400000000000000000000557611314633611600206530ustar00rootroot00000000000000# -*- coding: utf-8 -*- """Tests for marshmallow.validate""" from __future__ import unicode_literals import re import pytest from marshmallow.compat import PY2 from marshmallow import validate, ValidationError @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://2001:db8::ff00:42:8329', 'http://www.example.com:8000/foo', ]) 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\\', 'ftp:///example.com/', 'ftps:///example.com/', 'http//example.org', 'http:///', 'http:/example.org', 'foo://example.org', '../icons/logo.gif', 'abc', '..', '/', ' ', '', None, ]) 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', ]) 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', '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) 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=set(['http', 'https', 'ws'])) assert validator(url) == 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=set(['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) as excinfo: validator('invalid') assert "invalid ain't an URL" in str(excinfo) 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') ) @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', u'josé@blah.com', u'δοκ.ιμή@παράδειγμα.δοκιμή', ]) def test_email_valid(valid_email): validator = validate.Email() assert validator(valid_email) == valid_email @pytest.mark.parametrize('invalid_email', [ '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) as excinfo: validator('invalid') assert 'invalid is not an email addy.' in str(excinfo) 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(1, 1)(1) == 1 with pytest.raises(ValidationError): validate.Range(2, 3)(1) with pytest.raises(ValidationError): validate.Range(2)(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(2, 2)(2) == 2 with pytest.raises(ValidationError): validate.Range(0, 1)(2) with pytest.raises(ValidationError): validate.Range(None, 1)(2) def test_range_custom_message(): v = validate.Range(2, 3, error='{input} is not between {min} and {max}') with pytest.raises(ValidationError) as excinfo: v(1) assert '1 is not between 2 and 3' in str(excinfo) v = validate.Range(2, None, error='{input} is less than {min}') with pytest.raises(ValidationError) as excinfo: v(1) assert '1 is less than 2' in str(excinfo) v = validate.Range(None, 3, error='{input} is greater than {max}') with pytest.raises(ValidationError) as excinfo: v(4) assert '4 is greater than 3' in str(excinfo) def test_range_repr(): assert ( repr(validate.Range(min=None, max=None, error=None)) == '' ) assert ( repr(validate.Range(min=1, max=3, error='foo')) == '' .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]) with pytest.raises(ValueError): validate.Length(1, None, equal=3)('foo') with pytest.raises(ValueError): validate.Length(None, 5, equal=3)('foo') with pytest.raises(ValueError): 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) as excinfo: v('foo') assert 'foo is not between 5 and 6' in str(excinfo) v = validate.Length(5, None, error='{input} is shorter than {min}') with pytest.raises(ValidationError) as excinfo: v('foo') assert 'foo is shorter than 5' in str(excinfo) v = validate.Length(None, 2, error='{input} is longer than {max}') with pytest.raises(ValidationError) as excinfo: v('foo') assert 'foo is longer than 2' in str(excinfo) v = validate.Length(None, None, equal=4, error='{input} does not have {equal}') with pytest.raises(ValidationError) as excinfo: v('foo') assert 'foo does not have 4' in str(excinfo) 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) as excinfo: v('b') assert 'b is not equal to a.' in str(excinfo) 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) as excinfo: v('a') assert 'a does not match [0-9]+' in str(excinfo) 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(object): 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) as excinfo: validate.Predicate('_false')(d) assert 'Invalid input.' in str(excinfo) 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(object): def _false(self): return False def __str__(self): return 'Dummy' d = Dummy() with pytest.raises(ValidationError) as excinfo: validate.Predicate('_false', error='{input}.{method} is invalid!')(d) assert 'Dummy._false is invalid!' in str(excinfo) 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', {str('zoo') if PY2 else '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) as excinfo: validate.NoneOf([1, 2, 3])(3) assert 'Invalid input.' in str(excinfo) 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) as excinfo: validate.NoneOf([1, 2], error='')(1) assert '' in str(excinfo) none_of = validate.NoneOf( [1, 2], error='{input} cannot be one of {values}' ) with pytest.raises(ValidationError) as excinfo: none_of(1) assert '1 cannot be one of 1, 2' in str(excinfo) 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) as excinfo: validate.OneOf([1, 2, 3])(4) assert 'Not a valid choice.' in str(excinfo) 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('Not a valid choice.') ) 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): 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 acceptable.') ) assert ( repr(validate.ContainsOnly(choices=[1, 2, 3], labels=['a', 'b', 'c'], error='foo')) == '' .format(['a', 'b', 'c'], 'foo') ) marshmallow-3.0.0b3/tox.ini000066400000000000000000000001431314633611600156020ustar00rootroot00000000000000[tox] envlist=py27,py34,py35,pypy [testenv] deps= -rdev-requirements.txt commands= invoke test