[
  {
    "path": ".gitignore",
    "content": "*.py[cod]\n\n# C extensions\n*.so\n\n# Packages\n*.egg\n*.egg-info\ndist\nbuild\neggs\nparts\nbin\nvar\nsdist\ndevelop-eggs\n.installed.cfg\nlib\nlib64\n\n# Installer logs\npip-log.txt\n\n# Unit test / coverage reports\n.coverage\n.tox\n.cache\nnosetests.xml\n\n# Translations\n*.mo\n\n# Mr Developer\n.mr.developer.cfg\n.project\n.pydevproject\n\n# Complexity\noutput/*.html\noutput/*/index.html\n\n# Sphinx\ndocs/_build\nREADME.html\n\n_sandbox\n.konchrc\n\n# Virtual Environment\nenv\nvenv\n.python-version\n"
  },
  {
    "path": ".pre-commit-config.yaml",
    "content": "repos:\n- repo: https://github.com/asottile/pyupgrade\n  rev: v2.31.0\n  hooks:\n  - id: pyupgrade\n    args: [--py36-plus]\n- repo: https://github.com/python/black\n  rev: 22.1.0\n  hooks:\n  - id: black\n    language_version: python3\n- repo: https://gitlab.com/pycqa/flake8\n  rev: 3.9.2\n  hooks:\n  - id: flake8\n    additional_dependencies: [flake8-bugbear==22.1.11]\n- repo: https://github.com/asottile/blacken-docs\n  rev: v1.12.1\n  hooks:\n  - id: blacken-docs\n    additional_dependencies: [black==22.1.0]\n"
  },
  {
    "path": ".readthdocs.yml",
    "content": "version: 2\nsphinx:\n  configuration: docs/conf.py\nformats: all\npython:\n  version: 3.7\n  install:\n    - requirements: docs/requirements.txt\n"
  },
  {
    "path": "AUTHORS.rst",
    "content": "*******\nAuthors\n*******\n\nLead\n====\n\n- Steven Loria `@sloria <https://github.com/sloria>`_\n\nContributors (chronological)\n============================\n\n- Jotham Apaloo `@jo-tham <https://github.com/jo-tham>`_\n- Anders Steinlein `@asteinlein <https://github.com/asteinlein>`_\n- `@floqqi <https://github.com/floqqi>`_\n- Colton Allen `@cmanallen <https://github.com/cmanallen>`_\n- Dominik Steinberger `@ZeeD26 <https://github.com/ZeeD26>`_\n- Tim Mundt `@Tim-Erwin <https://github.com/Tim-Erwin>`_\n- Brandon Wood `@woodb <https://github.com/woodb>`_\n- Frazer McLean `@RazerM <https://github.com/RazerM>`_\n- J Rob Gant `@rgant <https://github.com/rgant>`_\n- Dan Poland `@danpoland <https://github.com/danpoland>`_\n- Pierre CHAISY `@akira-dev <https://github.com/akira-dev>`_\n- `@mrhanky17 <https://github.com/mrhanky17>`_\n- Mark Hall `@scmmmh <https://github.com/scmmmh>`_\n- Scott Werner `@scottwernervt <https://github.com/scottwernervt>`_\n- Michael Dodsworth `@mdodsworth <https://github.com/mdodsworth>`_\n- Mathieu Alorent `@kumy <https://github.com/kumy>`_\n- Grant Harris `@grantHarris <https://github.com/grantHarris>`_\n- Robert Sawicki `@ww3pl <https://github.com/ww3pl>`_\n- `@aberres <https://github.com/aberres>`_\n- George Alton `@georgealton <https://github.com/georgealton>`_\n- Areeb Jamal `@iamareebjamal <https://github.com/iamareebjamal>`_\n- Suren Khorenyan `@mahenzon <https://github.com/mahenzon>`_\n- Karthikeyan Singaravelan `@tirkarthi <https://github.com/tirkarthi>`_\n"
  },
  {
    "path": "CHANGELOG.rst",
    "content": "*********\nChangelog\n*********\n\n0.24.0 (2020-12-27)\n===================\n\nDeprecations/Removals:\n\n* Drop support for marshmallow 2, which is now EOL (:pr:`332`).\n\nBug fixes:\n\n* Fix behavior when serializing ``None`` (:pr:`302`). Thanks :user:`mahenzon`.\n\nOther changes:\n\n* Test against Python 3.8 and 3.9 (:pr:`332`).\n\n0.23.2 (2020-07-20)\n===================\n\nBug fixes:\n\n* Import from `collections.abc` for forward-compatibility with Python 3.10 (:issue:`318`).\n  Thanks :user:`tirkarthi`.\n\n0.23.1 (2020-03-22)\n===================\n\nBug fixes:\n\n* Fix nested fields validation error formatting (:issue:`120`).\n  Thanks :user:`mahenzon` and :user:`debonzi` for the PRs.\n\n0.23.0 (2020-02-02)\n===================\n\n* Improve performance of link generation from `Relationship` (:issue:`277`).\n  Thanks :user:`iamareebjamal` for reporting and fixing.\n\n0.22.0 (2019-09-15)\n===================\n\nDeprecation/Removals:\n\n* Drop support for Python 2.7 and 3.5.\n  Only Python>=3.6 is supported (:issue:`251`).\n* Drop support for marshmallow 3 pre-releases. Only stable versions >=2.15.2 are supported.\n* Remove ``fields.Meta``.\n\nBug fixes:\n\n* Address ``DeprecationWarning`` raised by ``Field.fail`` on marshmallow 3.\n\n0.21.2 (2019-07-01)\n===================\n\nBug fixes:\n\n* marshmallow 3.0.0rc7 compatibility (:pr:`233`).\n\nOther changes:\n\n* Format with pyupgrade and black (:pr:`235`).\n* Switch to Azure Pipelines for CI (:pr:`234`).\n\n0.21.1 (2019-05-05)\n===================\n\nBug fixes:\n\n* marshmallow 3.0.0rc6 cmpatibility (:pr:`221`).\n\n0.21.0 (2018-12-16)\n===================\n\nBug fixes:\n\n* *Backwards-incompatible*: Revert URL quoting introduced in 0.20.2\n  (:issue:`184`). If you need quoting, override `Schema.generate_url`.\n\n.. code-block:: python\n\n  from marshmallow_jsonapi import Schema\n  from werkzeug.urls import url_fix\n\n\n  class MySchema(Schema):\n      def generate_url(self, link, **kwargs):\n          url = super().generate_url(link, **kwargs)\n          return url_fix(url)\n\nThanks :user:`kgutwin` for reporting the issue.\n\n* Fix `Relationship` deserialization behavior when ``required=False`` (:issue:`177`).\n  Thanks :user:`aberres` for reporting and :user:`scottwernervt` for the\n  fix.\n\nOther changes:\n\n* Test against Python 3.7.\n\n0.20.5 (2018-10-27)\n===================\n\nBug fixes:\n\n* Fix deserializing ``id`` field to non-string types (:pr:`179`).\n  Thanks :user:`aberres` for the catch and patch.\n\n0.20.4 (2018-10-04)\n===================\n\nBug fixes:\n\n* Fix bug where multi-level nested relationships would not be properly\n  deserialized (:issue:`127`). Thanks :user:`ww3pl` for the catch and\n  patch.\n\n0.20.3 (2018-09-13)\n===================\n\nBug fixes:\n\n* Fix missing load validation when data is not a collection\n  but many=True (:pr:`161`). Thanks :user:`grantHarris`.\n\n0.20.2 (2018-08-15)\n===================\n\nBug fixes:\n\n* Fix issues where generated URLs are unquoted (:pr:`147`). Thanks\n  :user:`grantHarris`.\n\nOther changes:\n\n* Fix tests against marshmallow 3.0.0b13.\n\n0.20.1 (2018-07-15)\n===================\n\nBug fixes:\n\n* Fix deserializing ``missing`` with a `Relationship` field (:issue:`130`).\n  Thanks :user:`kumy` for the catch and patch.\n\n0.20.0 (2018-06-10)\n===================\n\nBug fixes:\n\n* Fix serialization of ``id`` for ``Relationship`` fields when\n  ``attribute`` is set (:issue:`69`). Thanks :user:`jordal` for\n  reporting and thanks :user:`scottwernervt` for the fix.\n\nNote: The above fix could break some code that set\n``Relationship.id_field`` before instantiating it.\nSet ``Relationship.default_id_field`` instead.\n\n.. code-block:: python\n\n\n    # before\n    fields.Relationship.id_field = \"item_id\"\n\n    # after\n    fields.Relationship.default_id_field = \"item_id\"\n\n\nSupport:\n\n* Test refactoring and various doc improvements (:issue:`63`, :issue:`86`,\n  :issue:`121,` and :issue:`122`). Thanks :user:`scottwernervt`.\n\n0.19.0 (2018-05-27)\n===================\n\nFeatures:\n\n* Schemas passed to ``fields.Relationship`` will inherit context from\n  the parent schema (:issue:`84`). Thanks :user:`asteinlein` and\n  :user:`scottwernervt` for the PRs.\n\n0.18.0 (2018-05-19)\n===================\n\nFeatures:\n\n* Add ``fields.ResourceMeta`` for serializing a resource-level meta\n  object (:issue:`107`). Thanks :user:`scottwernervt`.\n\nOther changes:\n\n* *Backwards-incompatible*: Drop official support for Python 3.4.\n\n0.17.0 (2018-04-29)\n===================\n\nFeatures:\n\n* Add support for marshmallow 3 (:issue:`97`). Thanks :user:`rockmnew`.\n* Thanks :user:`mdodsworth` for helping with :issue:`101`.\n* Move meta information object to document top level (:issue:`95`). Thanks :user:`scottwernervt`.\n\n0.16.0 (2017-11-08)\n===================\n\nFeatures:\n\n* Add support for exluding or including nested fields on relationships\n  (:issue:`94`). Thanks :user:`scottwernervt` for the PR.\n\nOther changes:\n\n* *Backwards-incompatible*: Drop support for marshmallow<2.8.0\n\n0.15.1 (2017-08-23)\n===================\n\nBug fixes:\n\n* Fix pointer for ``id`` in error objects (:issue:`90`). Thanks\n  :user:`rgant` for the catch and patch.\n\n0.15.0 (2017-06-27)\n===================\n\nFeatures:\n\n* ``Relationship`` field supports deserializing included data\n  (:issue:`83`). Thanks :user:`anuragagarwal561994` for the suggestion\n  and thanks :user:`asteinlein` for the PR.\n\n0.14.0 (2017-04-30)\n===================\n\nFeatures:\n\n* ``Relationship`` respects its passed ``Schema's`` ``get_attribute`` method when getting the ``id`` field for resource linkages (:issue:`80`). Thanks :user:`scmmmh` for the PR.\n\n0.13.0 (2017-04-18)\n===================\n\nFeatures:\n\n* Add support for including deeply nested relationships in compount documents (:issue:`61`). Thanks :user:`mrhanky17` for the PR.\n\n0.12.0 (2017-04-16)\n===================\n\nFeatures:\n\n* Use default attribute value instead of raising exception if relationship is ``None`` on ``Relationship`` field (:issue:`75`). Thanks :user:`akira-dev`.\n\n0.11.1 (2017-04-06)\n===================\n\nBug fixes:\n\n- Fix formatting JSON pointer when serializing an invalid object at index 0 (:issue:`77`). Thanks :user:`danpoland` for the catch and patch.\n\n0.11.0 (2017-03-12)\n===================\n\nBug fixes:\n\n* Fix compatibility with marshmallow 3.x.\n\n\nOther changes:\n\n* *Backwards-incompatible*: Remove unused `utils.get_value_or_raise` function.\n\n0.10.2 (2017-03-08)\n===================\n\nBug fixes:\n\n* Fix format of error object returned when ``data`` key is not included in input (:issue:`66`). Thanks :user:`RazerM`.\n* Fix serializing compound documents when ``Relationship`` is passed a schema class and ``many=True`` (:issue:`67`). Thanks :user:`danpoland` for the catch and patch.\n\n0.10.1 (2017-02-05)\n===================\n\nBug fixes:\n\n* Serialize ``None`` and empty lists (``[]``) to valid JSON-API objects (:issue:`58`). Thanks :user:`rgant` for reporting and sending a PR.\n\n0.10.0 (2017-01-05)\n===================\n\nFeatures:\n\n* Add ``fields.Meta`` for (de)serializing ``meta`` data on resource objects (:issue:`28`). Thanks :user:`rubdos` for the suggestion and initial work. Thanks :user:`RazerM` for the PR.\n\nOther changes:\n\n* Test against Python 3.6.\n\n0.9.0 (2016-10-08)\n==================\n\nFeatures:\n\n* Add Flask-specific schema with class Meta options for self link generation: ``self_view``, ``self_view_kwargs``, and ``self_view_many`` (:issue:`51`). Thanks :user:`asteinlein`.\n\nBug fixes:\n\n* Fix formatting of validation error messages on newer versions of marshmallow.\n\nOther changes:\n\n* Drop official support for Python 3.3.\n\n0.8.0 (2016-06-20)\n==================\n\nFeatures:\n\n* Add support for compound documents (:issue:`11`). Thanks :user:`Tim-Erwin` and :user:`woodb` for implementing this.\n* *Backwards-incompatible*: Remove ``include_data`` parameter from ``Relationship``. Use ``include_resource_linkage`` instead.\n\n0.7.1 (2016-05-08)\n==================\n\nBug fixes:\n\n* Format correction for error objects (:issue:`47`). Thanks :user:`ZeeD26` for the PR.\n\n0.7.0 (2016-04-03)\n==================\n\nFeatures:\n\n* Correctly format ``messages`` attribute of ``ValidationError`` raised when ``type`` key is missing in input (:issue:`43`). Thanks :user:`ZeeD26` for the catch and patch.\n* JSON pointers for error objects for relationships will point to the ``data`` key (:issue:`41`). Thanks :user:`cmanallen` for the PR.\n\n0.6.0 (2016-03-24)\n==================\n\nFeatures:\n\n* ``Relationship`` deserialization improvements: properly validate to-one and to-many relatinoships and validate the presense of the ``data`` key (:issue:`37`). Thanks :user:`cmanallen` for the PR.\n* ``attributes`` is no longer a required key in the ``data`` object (:issue:`#39`, :issue:`42`). Thanks :user:`ZeeD26` for reporting and :user:`cmanallen` for the PR.\n* Added ``id`` serialization (:issue:`39`). Thanks again :user:`cmanallen`.\n\n0.5.0 (2016-02-08)\n==================\n\nFeatures:\n\n* Add relationship deserialization (:issue:`15`).\n* Allow serialization of foreign key attributes (:issue:`32`).\n* Relationship IDs serialize to strings, as is required by JSON-API (:issue:`31`).\n* ``Relationship`` field respects ``dump_to`` parameter (:issue:`33`).\n\nThanks :user:`cmanallen` for all of these changes.\n\nOther changes:\n\n* The minimum supported marshmallow version is 2.3.0.\n\n0.4.2 (2015-12-21)\n==================\n\nBug fixes:\n\n* Relationship names are inflected when appropriate (:issue:`22`). Thanks :user:`angelosarto` for reporting.\n\n0.4.1 (2015-12-19)\n==================\n\nBug fixes:\n\n* Fix serializing null and empty relationships with ``flask.Relationship`` (:issue:`24`). Thanks :user:`floqqi` for the catch and patch.\n\n0.4.0 (2015-12-06)\n==================\n\n* Correctly serialize null and empty relationships (:issue:`10`). Thanks :user:`jo-tham` for the PR.\n* Add ``self_url``, ``self_url_kwargs``, and ``self_url_many`` class Meta options for adding ``self`` links. Thanks :user:`asteinlein` for the PR.\n\n0.3.0 (2015-10-18)\n==================\n\n* *Backwards-incompatible*: Replace ``HyperlinkRelated`` with ``Relationship`` field. Supports related links (``related``), relationship links (``self``), and resource linkages.\n* *Backwards-incompatible*: Validate and deserialize JSON API-formatted request payloads.\n* Fix error formatting when ``many=True``.\n* Fix error formatting in strict mode.\n\n0.2.2 (2015-09-26)\n==================\n\n* Fix for marshmallow 2.0.0 compat.\n\n0.2.1 (2015-09-16)\n==================\n\n* Compatibility with marshmallow>=2.0.0rc2.\n\n0.2.0 (2015-09-13)\n==================\n\nFeatures:\n\n* Add framework-independent ``HyperlinkRelated`` field.\n* Support inflection of attribute names via the ``inflect`` class Meta option.\n\nBug fixes:\n\n* Fix for making ``HyperlinkRelated`` read-only by defualt.\n\nSupport:\n\n* Docs updates.\n* Tested on Python 3.5.\n\n0.1.0 (2015-09-12)\n==================\n\n* First PyPI release.\n* Include Schema that serializes objects to resource objects.\n* Flask-compatible HyperlinkRelate field for serializing relationships.\n* Errors are formatted as JSON API errror objects.\n"
  },
  {
    "path": "CONTRIBUTING.rst",
    "content": "Contributing Guidelines\n=======================\n\nQuestions, Feature Requests, Bug Reports, and Feedback…\n-------------------------------------------------------\n\n…should all be reported on the `Github Issue Tracker`_ .\n\n.. _`Github Issue Tracker`: https://github.com/marshmallow-code/marshmallow-jsonapi/issues?state=open\n\nSetting Up for Local Development\n--------------------------------\n\n1. Fork marshmallow-jsonapi_ on Github.\n\n::\n\n    $ git clone https://github.com/marshmallow-code/marshmallow-jsonapi.git\n    $ cd marshmallow-jsonapi\n\n2. Install development requirements. **It is highly recommended that you use a virtualenv.**\n   Use the following command to install an editable version of\n   marshmallow-jsonapi along with its development requirements.\n\n::\n\n    # After activating your virtualenv\n    $ pip install -e '.[dev]'\n\n3. Install the pre-commit hooks, which will format and lint your git staged files.\n\n::\n\n    # The pre-commit CLI was installed above\n    $ pre-commit install\n\nGit Branch Structure\n--------------------\n\nMarshmallow abides by the following branching model:\n\n\n``dev``\n    Current development branch. **New features should branch off here**.\n\n``X.Y-line``\n    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.\n\n**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.\n\nPull Requests\n--------------\n\n1. Create a new local branch.\n::\n\n    $ git checkout -b name-of-feature dev\n\n2. Commit your changes. Write `good commit messages <http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html>`_.\n::\n\n    $ git commit -m \"Detailed commit message\"\n    $ git push origin name-of-feature\n\n3. Before submitting a pull request, check the following:\n\n- If the pull request adds functionality, it is tested and the docs are updated.\n- You've added yourself to ``AUTHORS.rst``.\n\n4. Submit a pull request to ``marshmallow-code:dev`` or the appropriate maintenance branch. The `CI <https://dev.azure.com/sloria/sloria/_build/latest?definitionId=7&branchName=dev>`_ build must be passing before your pull request is merged.\n\nRunning tests\n-------------\n\nTo run all To run all tests: ::\n\n    $ pytest\n\nTo run syntax checks: ::\n\n    $ tox -e lint\n\n(Optional) To run tests in all supported Python versions in their own virtual environments (must have each interpreter installed): ::\n\n    $ tox\n\nDocumentation\n-------------\n\nContributions to the documentation are welcome. Documentation is written in `reStructuredText`_ (rST). A quick rST reference can be found `here <https://docutils.sourceforge.io/docs/user/rst/quickref.html>`_. Builds are powered by Sphinx_.\n\nTo build the docs in \"watch\" mode: ::\n\n   $ tox -e watch-docs\n\nChanges in the `docs/` directory will automatically trigger a rebuild.\n\n.. _Sphinx: http://sphinx.pocoo.org/\n.. _`reStructuredText`: https://docutils.sourceforge.io/rst.html\n.. _marshmallow-jsonapi: https://github.com/marshmallow-code/marshmallow-jsonapi\n"
  },
  {
    "path": "LICENSE",
    "content": "Copyright 2015-2020 Steven Loria and contributors\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "MANIFEST.in",
    "content": "include *.rst LICENSE\nrecursive-include tests *\nrecursive-include docs *\nrecursive-include examples *\nrecursive-exclude docs *.pyc\nrecursive-exclude docs *.pyo\nrecursive-exclude tests *.pyc\nrecursive-exclude tests *.pyo\nrecursive-exclude examples *.pyc\nrecursive-exclude examples *.pyo\nprune docs/_build\n"
  },
  {
    "path": "README.rst",
    "content": "*******************\nmarshmallow-jsonapi\n*******************\n\n.. image:: https://badgen.net/pypi/v/marshmallow-jsonapi\n    :target: https://pypi.org/project/marshmallow-jsonapi/\n    :alt: PyPI version\n\n.. image:: https://dev.azure.com/sloria/sloria/_apis/build/status/marshmallow-code.marshmallow-jsonapi?branchName=dev\n    :target: https://dev.azure.com/sloria/sloria/_build/latest?definitionId=7&branchName=dev\n    :alt: Build status\n\n.. image:: https://readthedocs.org/projects/marshmallow-jsonapi/badge/\n   :target: https://marshmallow-jsonapi.readthedocs.io/\n   :alt: Documentation\n\n.. image:: https://badgen.net/badge/marshmallow/3\n    :target: https://marshmallow.readthedocs.io/en/latest/upgrading.html\n    :alt: marshmallow 3 compatible\n\n.. image:: https://badgen.net/badge/code%20style/black/000\n    :target: https://github.com/ambv/black\n    :alt: code style: black\n\nHomepage: http://marshmallow-jsonapi.readthedocs.io/\n\nJSON API 1.0 (`https://jsonapi.org <http://jsonapi.org/>`_) formatting with `marshmallow <https://marshmallow.readthedocs.io>`_.\n\nmarshmallow-jsonapi provides a simple way to produce JSON API-compliant data in any Python web framework.\n\n.. code-block:: python\n\n    from marshmallow_jsonapi import Schema, fields\n\n\n    class PostSchema(Schema):\n        id = fields.Str(dump_only=True)\n        title = fields.Str()\n\n        author = fields.Relationship(\n            \"/authors/{author_id}\", related_url_kwargs={\"author_id\": \"<author.id>\"}\n        )\n\n        comments = fields.Relationship(\n            \"/posts/{post_id}/comments\",\n            related_url_kwargs={\"post_id\": \"<id>\"},\n            # Include resource linkage\n            many=True,\n            include_resource_linkage=True,\n            type_=\"comments\",\n        )\n\n        class Meta:\n            type_ = \"posts\"\n\n\n    post_schema = PostSchema()\n    post_schema.dump(post)\n    # {\n    #     \"data\": {\n    #         \"id\": \"1\",\n    #         \"type\": \"posts\"\n    #         \"attributes\": {\n    #             \"title\": \"JSON API paints my bikeshed!\"\n    #         },\n    #         \"relationships\": {\n    #             \"author\": {\n    #                 \"links\": {\n    #                     \"related\": \"/authors/9\"\n    #                 }\n    #             },\n    #             \"comments\": {\n    #                 \"links\": {\n    #                     \"related\": \"/posts/1/comments/\"\n    #                 }\n    #                 \"data\": [\n    #                     {\"id\": 5, \"type\": \"comments\"},\n    #                     {\"id\": 12, \"type\": \"comments\"}\n    #                 ],\n    #             }\n    #         },\n    #     }\n    # }\n\nInstallation\n============\n::\n\n    pip install marshmallow-jsonapi\n\n\nDocumentation\n=============\n\nFull documentation is available at https://marshmallow-jsonapi.readthedocs.io/.\n\nRequirements\n============\n\n- Python >= 3.6\n\nProject Links\n=============\n\n- Docs: http://marshmallow-jsonapi.readthedocs.io/\n- Changelog: http://marshmallow-jsonapi.readthedocs.io/en/latest/changelog.html\n- Contributing Guidelines: https://marshmallow-jsonapi.readthedocs.io/en/latest/contributing.html\n- PyPI: https://pypi.python.org/pypi/marshmallow-jsonapi\n- Issues: https://github.com/marshmallow-code/marshmallow-jsonapi/issues\n\nLicense\n=======\n\nMIT licensed. See the bundled `LICENSE <https://github.com/marshmallow-code/marshmallow-jsonapi/blob/master/LICENSE>`_ file for more details.\n"
  },
  {
    "path": "azure-pipelines.yml",
    "content": "trigger:\n  branches:\n    include: [dev, test-me-*]\n  tags:\n    include: [\"*\"]\n\n# Run builds nightly to catch incompatibilities with new marshmallow releases\nschedules:\n  - cron: \"0 0 * * *\"\n    displayName: Daily midnight build\n    branches:\n      include:\n        - dev\n    always: \"true\"\n\nresources:\n  repositories:\n    - repository: sloria\n      type: github\n      endpoint: github\n      name: sloria/azure-pipeline-templates\n      ref: refs/heads/sloria\n\njobs:\n  - template: job--python-tox.yml@sloria\n    parameters:\n      toxenvs:\n        - lint\n\n        - py36-marshmallow3\n        - py37-marshmallow3\n        - py38-marshmallow3\n        - py39-marshmallow3\n        - py39-marshmallowdev\n\n        - docs\n\n      os: linux\n  - template: job--pypi-release.yml@sloria\n    parameters:\n      dependsOn:\n        - tox_linux\n"
  },
  {
    "path": "docs/Makefile",
    "content": "# Makefile for Sphinx documentation\n#\n\n# You can set these variables from the command line.\nSPHINXOPTS    =\nSPHINXBUILD   = sphinx-build\nPAPER         =\nBUILDDIR      = _build\n\n# User-friendly check for sphinx-build\nifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)\n$(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/)\nendif\n\n# Internal variables.\nPAPEROPT_a4     = -D latex_paper_size=a4\nPAPEROPT_letter = -D latex_paper_size=letter\nALLSPHINXOPTS   = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .\n# the i18n builder cannot share the environment and doctrees with the others\nI18NSPHINXOPTS  = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .\n\n.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext\n\nhelp:\n\t@echo \"Please use \\`make <target>' where <target> is one of\"\n\t@echo \"  html       to make standalone HTML files\"\n\t@echo \"  dirhtml    to make HTML files named index.html in directories\"\n\t@echo \"  singlehtml to make a single large HTML file\"\n\t@echo \"  pickle     to make pickle files\"\n\t@echo \"  json       to make JSON files\"\n\t@echo \"  htmlhelp   to make HTML files and a HTML help project\"\n\t@echo \"  qthelp     to make HTML files and a qthelp project\"\n\t@echo \"  devhelp    to make HTML files and a Devhelp project\"\n\t@echo \"  epub       to make an epub\"\n\t@echo \"  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter\"\n\t@echo \"  latexpdf   to make LaTeX files and run them through pdflatex\"\n\t@echo \"  latexpdfja to make LaTeX files and run them through platex/dvipdfmx\"\n\t@echo \"  text       to make text files\"\n\t@echo \"  man        to make manual pages\"\n\t@echo \"  texinfo    to make Texinfo files\"\n\t@echo \"  info       to make Texinfo files and run them through makeinfo\"\n\t@echo \"  gettext    to make PO message catalogs\"\n\t@echo \"  changes    to make an overview of all changed/added/deprecated items\"\n\t@echo \"  xml        to make Docutils-native XML files\"\n\t@echo \"  pseudoxml  to make pseudoxml-XML files for display purposes\"\n\t@echo \"  linkcheck  to check all external links for integrity\"\n\t@echo \"  doctest    to run all doctests embedded in the documentation (if enabled)\"\n\nclean:\n\trm -rf $(BUILDDIR)/*\n\nhtml:\n\t$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html\n\t@echo\n\t@echo \"Build finished. The HTML pages are in $(BUILDDIR)/html.\"\n\ndirhtml:\n\t$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml\n\t@echo\n\t@echo \"Build finished. The HTML pages are in $(BUILDDIR)/dirhtml.\"\n\nsinglehtml:\n\t$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml\n\t@echo\n\t@echo \"Build finished. The HTML page is in $(BUILDDIR)/singlehtml.\"\n\npickle:\n\t$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle\n\t@echo\n\t@echo \"Build finished; now you can process the pickle files.\"\n\njson:\n\t$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json\n\t@echo\n\t@echo \"Build finished; now you can process the JSON files.\"\n\nhtmlhelp:\n\t$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp\n\t@echo\n\t@echo \"Build finished; now you can run HTML Help Workshop with the\" \\\n\t      \".hhp project file in $(BUILDDIR)/htmlhelp.\"\n\nqthelp:\n\t$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp\n\t@echo\n\t@echo \"Build finished; now you can run \"qcollectiongenerator\" with the\" \\\n\t      \".qhcp project file in $(BUILDDIR)/qthelp, like this:\"\n\t@echo \"# qcollectiongenerator $(BUILDDIR)/qthelp/complexity.qhcp\"\n\t@echo \"To view the help file:\"\n\t@echo \"# assistant -collectionFile $(BUILDDIR)/qthelp/complexity.qhc\"\n\ndevhelp:\n\t$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp\n\t@echo\n\t@echo \"Build finished.\"\n\t@echo \"To view the help file:\"\n\t@echo \"# mkdir -p $$HOME/.local/share/devhelp/complexity\"\n\t@echo \"# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/complexity\"\n\t@echo \"# devhelp\"\n\nepub:\n\t$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub\n\t@echo\n\t@echo \"Build finished. The epub file is in $(BUILDDIR)/epub.\"\n\nlatex:\n\t$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex\n\t@echo\n\t@echo \"Build finished; the LaTeX files are in $(BUILDDIR)/latex.\"\n\t@echo \"Run \\`make' in that directory to run these through (pdf)latex\" \\\n\t      \"(use \\`make latexpdf' here to do that automatically).\"\n\nlatexpdf:\n\t$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex\n\t@echo \"Running LaTeX files through pdflatex...\"\n\t$(MAKE) -C $(BUILDDIR)/latex all-pdf\n\t@echo \"pdflatex finished; the PDF files are in $(BUILDDIR)/latex.\"\n\nlatexpdfja:\n\t$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex\n\t@echo \"Running LaTeX files through platex and dvipdfmx...\"\n\t$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja\n\t@echo \"pdflatex finished; the PDF files are in $(BUILDDIR)/latex.\"\n\ntext:\n\t$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text\n\t@echo\n\t@echo \"Build finished. The text files are in $(BUILDDIR)/text.\"\n\nman:\n\t$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man\n\t@echo\n\t@echo \"Build finished. The manual pages are in $(BUILDDIR)/man.\"\n\ntexinfo:\n\t$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo\n\t@echo\n\t@echo \"Build finished. The Texinfo files are in $(BUILDDIR)/texinfo.\"\n\t@echo \"Run \\`make' in that directory to run these through makeinfo\" \\\n\t      \"(use \\`make info' here to do that automatically).\"\n\ninfo:\n\t$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo\n\t@echo \"Running Texinfo files through makeinfo...\"\n\tmake -C $(BUILDDIR)/texinfo info\n\t@echo \"makeinfo finished; the Info files are in $(BUILDDIR)/texinfo.\"\n\ngettext:\n\t$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale\n\t@echo\n\t@echo \"Build finished. The message catalogs are in $(BUILDDIR)/locale.\"\n\nchanges:\n\t$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes\n\t@echo\n\t@echo \"The overview file is in $(BUILDDIR)/changes.\"\n\nlinkcheck:\n\t$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck\n\t@echo\n\t@echo \"Link check complete; look for any errors in the above output \" \\\n\t      \"or in $(BUILDDIR)/linkcheck/output.txt.\"\n\ndoctest:\n\t$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest\n\t@echo \"Testing of doctests in the sources finished, look at the \" \\\n\t      \"results in $(BUILDDIR)/doctest/output.txt.\"\n\nxml:\n\t$(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml\n\t@echo\n\t@echo \"Build finished. The XML files are in $(BUILDDIR)/xml.\"\n\npseudoxml:\n\t$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml\n\t@echo\n\t@echo \"Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml.\"\n"
  },
  {
    "path": "docs/api_reference.rst",
    "content": ".. _api:\n\n*************\nAPI Reference\n*************\n\nCore\n====\n\n.. automodule:: marshmallow_jsonapi\n    :members:\n\nFields\n======\n\n.. automodule:: marshmallow_jsonapi.fields\n    :members:\n\nFlask\n=====\n\n.. automodule:: marshmallow_jsonapi.flask\n    :members:\n\nExceptions\n==========\n\n.. automodule:: marshmallow_jsonapi.exceptions\n    :members:\n\nUtilities\n=========\n\n.. automodule:: marshmallow_jsonapi.utils\n    :members:\n"
  },
  {
    "path": "docs/authors.rst",
    "content": ".. include:: ../AUTHORS.rst\n"
  },
  {
    "path": "docs/changelog.rst",
    "content": ".. _changelog:\n\n.. include:: ../CHANGELOG.rst\n"
  },
  {
    "path": "docs/conf.py",
    "content": "import datetime as dt\nimport os\nimport sys\n\nsys.path.insert(0, os.path.abspath(\"..\"))\nimport marshmallow_jsonapi  # noqa: E402\n\nextensions = [\n    \"sphinx.ext.autodoc\",\n    \"sphinx.ext.intersphinx\",\n    \"sphinx.ext.viewcode\",\n    \"sphinx_issues\",\n]\n\nprimary_domain = \"py\"\ndefault_role = \"py:obj\"\n\nintersphinx_mapping = {\n    \"python\": (\"http://python.readthedocs.io/en/latest/\", None),\n    \"marshmallow\": (\"http://marshmallow.readthedocs.io/en/latest/\", None),\n}\n\nissues_github_path = \"marshmallow-code/marshmallow-jsonapi\"\n\nsource_suffix = \".rst\"\nmaster_doc = \"index\"\nproject = \"marshmallow-jsonapi\"\ncopyright = f\"Steven Loria {dt.datetime.utcnow():%Y}\"\n\nversion = release = marshmallow_jsonapi.__version__\n\nexclude_patterns = [\"_build\"]\n\n# THEME\n\n# on_rtd is whether we are on readthedocs.org\non_rtd = os.environ.get(\"READTHEDOCS\", None) == \"True\"\n\nif not on_rtd:  # only import and set the theme if we're building docs locally\n    import sphinx_rtd_theme\n\n    html_theme = \"sphinx_rtd_theme\"\n    html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]\n"
  },
  {
    "path": "docs/contributing.rst",
    "content": ".. include:: ../CONTRIBUTING.rst\n"
  },
  {
    "path": "docs/index.rst",
    "content": "*******************\nmarshmallow-jsonapi\n*******************\n\nRelease v\\ |version|. (:ref:`Changelog <changelog>`)\n\nJSON API 1.0 (`https://jsonapi.org <http://jsonapi.org/>`_) formatting with `marshmallow <https://marshmallow.readthedocs.io>`_.\n\nmarshmallow-jsonapi provides a simple way to produce JSON API-compliant data in any Python web framework.\n\n.. code-block:: python\n\n    from marshmallow_jsonapi import Schema, fields\n\n\n    class PostSchema(Schema):\n        id = fields.Str(dump_only=True)\n        title = fields.Str()\n\n        author = fields.Relationship(\n            related_url=\"/authors/{author_id}\",\n            related_url_kwargs={\"author_id\": \"<author.id>\"},\n        )\n\n        comments = fields.Relationship(\n            related_url=\"/posts/{post_id}/comments\",\n            related_url_kwargs={\"post_id\": \"<id>\"},\n            # Include resource linkage\n            many=True,\n            include_resource_linkage=True,\n            type_=\"comments\",\n        )\n\n        class Meta:\n            type_ = \"posts\"\n            strict = True\n\n\n    post_schema = PostSchema()\n    post_schema.dump(post)\n    # {\n    #     \"data\": {\n    #         \"id\": \"1\",\n    #         \"type\": \"posts\"\n    #         \"attributes\": {\n    #             \"title\": \"JSON API paints my bikeshed!\"\n    #         },\n    #         \"relationships\": {\n    #             \"author\": {\n    #                 \"links\": {\n    #                     \"related\": \"/authors/9\"\n    #                 }\n    #             },\n    #             \"comments\": {\n    #                 \"data\": [\n    #                     {\"id\": 5, \"type\": \"comments\"},\n    #                     {\"id\": 12, \"type\": \"comments\"}\n    #                 ],\n    #                 \"links\": {\n    #                     \"related\": \"/posts/1/comments/\"\n    #                 }\n    #             }\n    #         },\n    #     }\n    # }\n\nInstallation\n============\n::\n\n    pip install marshmallow-jsonapi\n\nGuide\n=====\n\n.. toctree::\n    :maxdepth: 2\n\n    quickstart\n\nAPI Reference\n=============\n\n.. toctree::\n   :maxdepth: 2\n\n   api_reference\n\nProject info\n============\n\n.. toctree::\n   :maxdepth: 1\n\n   changelog\n   authors\n   contributing\n   license\n\nLinks\n=====\n\n- `marshmallow-jsonapi @ GitHub <https://github.com/marshmallow-code/marshmallow-jsonapi>`_\n- `marshmallow-jsonapi @ PyPI <https://pypi.python.org/pypi/marshmallow-jsonapi>`_\n- `Issue Tracker <https://github.com/marshmallow-code/marshmallow-jsonapi/issues>`_\n"
  },
  {
    "path": "docs/license.rst",
    "content": "*******\nLicense\n*******\n\n.. literalinclude:: ../LICENSE\n"
  },
  {
    "path": "docs/make.bat",
    "content": "@ECHO OFF\n\nREM Command file for Sphinx documentation\n\nif \"%SPHINXBUILD%\" == \"\" (\n\tset SPHINXBUILD=sphinx-build\n)\nset BUILDDIR=_build\nset ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .\nset I18NSPHINXOPTS=%SPHINXOPTS% .\nif NOT \"%PAPER%\" == \"\" (\n\tset ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%\n\tset I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%\n)\n\nif \"%1\" == \"\" goto help\n\nif \"%1\" == \"help\" (\n\t:help\n\techo.Please use `make ^<target^>` where ^<target^> is one of\n\techo.  html       to make standalone HTML files\n\techo.  dirhtml    to make HTML files named index.html in directories\n\techo.  singlehtml to make a single large HTML file\n\techo.  pickle     to make pickle files\n\techo.  json       to make JSON files\n\techo.  htmlhelp   to make HTML files and a HTML help project\n\techo.  qthelp     to make HTML files and a qthelp project\n\techo.  devhelp    to make HTML files and a Devhelp project\n\techo.  epub       to make an epub\n\techo.  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter\n\techo.  text       to make text files\n\techo.  man        to make manual pages\n\techo.  texinfo    to make Texinfo files\n\techo.  gettext    to make PO message catalogs\n\techo.  changes    to make an overview over all changed/added/deprecated items\n\techo.  xml        to make Docutils-native XML files\n\techo.  pseudoxml  to make pseudoxml-XML files for display purposes\n\techo.  linkcheck  to check all external links for integrity\n\techo.  doctest    to run all doctests embedded in the documentation if enabled\n\tgoto end\n)\n\nif \"%1\" == \"clean\" (\n\tfor /d %%i in (%BUILDDIR%\\*) do rmdir /q /s %%i\n\tdel /q /s %BUILDDIR%\\*\n\tgoto end\n)\n\n\n%SPHINXBUILD% 2> nul\nif errorlevel 9009 (\n\techo.\n\techo.The 'sphinx-build' command was not found. Make sure you have Sphinx\n\techo.installed, then set the SPHINXBUILD environment variable to point\n\techo.to the full path of the 'sphinx-build' executable. Alternatively you\n\techo.may add the Sphinx directory to PATH.\n\techo.\n\techo.If you don't have Sphinx installed, grab it from\n\techo.http://sphinx-doc.org/\n\texit /b 1\n)\n\nif \"%1\" == \"html\" (\n\t%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The HTML pages are in %BUILDDIR%/html.\n\tgoto end\n)\n\nif \"%1\" == \"dirhtml\" (\n\t%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.\n\tgoto end\n)\n\nif \"%1\" == \"singlehtml\" (\n\t%SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.\n\tgoto end\n)\n\nif \"%1\" == \"pickle\" (\n\t%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished; now you can process the pickle files.\n\tgoto end\n)\n\nif \"%1\" == \"json\" (\n\t%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished; now you can process the JSON files.\n\tgoto end\n)\n\nif \"%1\" == \"htmlhelp\" (\n\t%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished; now you can run HTML Help Workshop with the ^\n.hhp project file in %BUILDDIR%/htmlhelp.\n\tgoto end\n)\n\nif \"%1\" == \"qthelp\" (\n\t%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished; now you can run \"qcollectiongenerator\" with the ^\n.qhcp project file in %BUILDDIR%/qthelp, like this:\n\techo.^> qcollectiongenerator %BUILDDIR%\\qthelp\\complexity.qhcp\n\techo.To view the help file:\n\techo.^> assistant -collectionFile %BUILDDIR%\\qthelp\\complexity.ghc\n\tgoto end\n)\n\nif \"%1\" == \"devhelp\" (\n\t%SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished.\n\tgoto end\n)\n\nif \"%1\" == \"epub\" (\n\t%SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The epub file is in %BUILDDIR%/epub.\n\tgoto end\n)\n\nif \"%1\" == \"latex\" (\n\t%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished; the LaTeX files are in %BUILDDIR%/latex.\n\tgoto end\n)\n\nif \"%1\" == \"latexpdf\" (\n\t%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex\n\tcd %BUILDDIR%/latex\n\tmake all-pdf\n\tcd %BUILDDIR%/..\n\techo.\n\techo.Build finished; the PDF files are in %BUILDDIR%/latex.\n\tgoto end\n)\n\nif \"%1\" == \"latexpdfja\" (\n\t%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex\n\tcd %BUILDDIR%/latex\n\tmake all-pdf-ja\n\tcd %BUILDDIR%/..\n\techo.\n\techo.Build finished; the PDF files are in %BUILDDIR%/latex.\n\tgoto end\n)\n\nif \"%1\" == \"text\" (\n\t%SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The text files are in %BUILDDIR%/text.\n\tgoto end\n)\n\nif \"%1\" == \"man\" (\n\t%SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The manual pages are in %BUILDDIR%/man.\n\tgoto end\n)\n\nif \"%1\" == \"texinfo\" (\n\t%SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.\n\tgoto end\n)\n\nif \"%1\" == \"gettext\" (\n\t%SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The message catalogs are in %BUILDDIR%/locale.\n\tgoto end\n)\n\nif \"%1\" == \"changes\" (\n\t%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.The overview file is in %BUILDDIR%/changes.\n\tgoto end\n)\n\nif \"%1\" == \"linkcheck\" (\n\t%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Link check complete; look for any errors in the above output ^\nor in %BUILDDIR%/linkcheck/output.txt.\n\tgoto end\n)\n\nif \"%1\" == \"doctest\" (\n\t%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Testing of doctests in the sources finished, look at the ^\nresults in %BUILDDIR%/doctest/output.txt.\n\tgoto end\n)\n\nif \"%1\" == \"xml\" (\n\t%SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The XML files are in %BUILDDIR%/xml.\n\tgoto end\n)\n\nif \"%1\" == \"pseudoxml\" (\n\t%SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml.\n\tgoto end\n)\n\n:end\n"
  },
  {
    "path": "docs/quickstart.rst",
    "content": "**********\nQuickstart\n**********\n\n.. note:: The following guide assumes some familiarity with the marshmallow API. To learn more about marshmallow, see its official documentation at `https://marshmallow.readthedocs.io <https://marshmallow.readthedocs.io>`_.\n\nDeclaring schemas\n=================\n\nLet’s start with a basic post “model”.\n\n.. code-block:: python\n\n    class Post:\n        def __init__(self, id, title):\n            self.id = id\n            self.title = title\n\nDeclare your schemas as you would with marshmallow.\n\nA :class:`.Schema` **MUST** define:\n\n- An ``id`` field\n- The ``type_`` class Meta option\n\nIt is **RECOMMENDED** to set strict mode to `True`.\n\nAutomatic self-linking is supported through these Meta options:\n\n- ``self_url`` specifies the URL to the resource itself\n- ``self_url_kwargs`` specifies replacement fields for `self_url`\n- ``self_url_many`` specifies the URL the resource when a collection (many) are\n  serialized\n\n.. code-block:: python\n\n    from marshmallow_jsonapi import Schema, fields\n\n\n    class PostSchema(Schema):\n        id = fields.Str(dump_only=True)\n        title = fields.Str()\n\n        class Meta:\n            type_ = \"posts\"\n            self_url = \"/posts/{id}\"\n            self_url_kwargs = {\"id\": \"<id>\"}\n            self_url_many = \"/posts/\"\n\nThese URLs can be auto-generated by specifying ``self_view``, ``self_view_kwargs``\nand ``self_view_many`` instead when using the :ref:`flask-integration`.\n\nSerialization\n=============\n\nObjects will be serialized to `JSON API documents <http://jsonapi.org/format/#document-structure>`_ with primary data.\n\n.. code-block:: python\n\n    post = Post(id=\"1\", title=\"Django is Omakase\")\n    PostSchema().dump(post)\n    # {\n    #     'data': {\n    #         'id': '1',\n    #         'type': 'posts',\n    #         'attributes': {'title': 'Django is Omakase'},\n    #         'links': {'self': '/posts/1'}\n    #     },\n    #     'links': {'self': '/posts/1'}\n    # }\n\nRelationships\n=============\n\nThe `Relationship <marshmallow_json.fields.Relationship>` field is used to serialize `relationship objects <http://jsonapi.org/format/#document-resource-object-relationships>`_. For example, a Post may have an author and comments associated with it.\n\n.. code-block:: python\n\n    class User:\n        def __init__(self, id, name):\n            self.id = id\n            self.name = name\n\n\n    class Comment:\n        def __init__(self, id, body, author):\n            self.id = id\n            self.body = body\n            self.author = author\n\n\n    class Post:\n        def __init__(self, id, title, author, comments=None):\n            self.id = id\n            self.title = title\n            self.author = author  # User object\n            self.comments = [] if comments is None else comments  # Comment objects\n\nTo serialize links, pass a URL format string and a dictionary of keyword arguments. String arguments enclosed in `< >` will be interpreted as attributes to pull from the object being serialized. The relationship links can automatically be generated from Flask view names when using the :ref:`flask-integration`.\n\n.. code-block:: python\n    :emphasize-lines: 5-10\n\n    class PostSchema(Schema):\n        id = fields.Str(dump_only=True)\n        title = fields.Str()\n\n        author = fields.Relationship(\n            self_url=\"/posts/{post_id}/relationships/author\",\n            self_url_kwargs={\"post_id\": \"<id>\"},\n            related_url=\"/authors/{author_id}\",\n            related_url_kwargs={\"author_id\": \"<author.id>\"},\n        )\n\n        class Meta:\n            type_ = \"posts\"\n\n\n    user = User(id=\"94\", name=\"Laura\")\n    post = Post(id=\"1\", title=\"Django is Omakase\", author=user)\n    PostSchema().dump(post)\n    # {\n    #     'data': {\n    #         'id': '1',\n    #         'type': 'posts',\n    #         'attributes': {'title': 'Django is Omakase'},\n    #         'relationships': {\n    #             'author': {\n    #                 'links': {\n    #                     'self': '/posts/1/relationships/author',\n    #                     'related': '/authors/94'\n    #                 }\n    #             }\n    #         }\n    #     }\n    # }\n\nResource linkages\n-----------------\n\nYou can serialize `resource linkages <http://jsonapi.org/format/#document-resource-object-linkage>`_ by passing ``include_resource_linkage=True`` and the resource ``type_`` argument.\n\n.. code-block:: python\n    :emphasize-lines: 10-12\n\n    class PostSchema(Schema):\n        id = fields.Str(dump_only=True)\n        title = fields.Str()\n\n        author = fields.Relationship(\n            self_url=\"/posts/{post_id}/relationships/author\",\n            self_url_kwargs={\"post_id\": \"<id>\"},\n            related_url=\"/authors/{author_id}\",\n            related_url_kwargs={\"author_id\": \"<author.id>\"},\n            # Include resource linkage\n            include_resource_linkage=True,\n            type_=\"users\",\n        )\n\n        class Meta:\n            type_ = \"posts\"\n\n\n    PostSchema().dump(post)\n    # {\n    #     'data': {\n    #         'id': '1',\n    #         'type': 'posts',\n    #         'attributes': {'title': 'Django is Omakase'},\n    #         'relationships': {\n    #             'author': {\n    #                 'data': {'type': 'users', 'id': '94'},\n    #                 'links': {\n    #                     'self': '/posts/1/relationships/author',\n    #                     'related': '/authors/94'\n    #                 }\n    #             }\n    #         }\n    #     }\n    # }\n\nCompound documents\n------------------\n\n`Compound documents <http://jsonapi.org/format/#document-compound-documents>`_ allow to include related resources into the request with the primary resource. In order to include objects, you have to define a :class:`.Schema` for the respective relationship, which will be used to render those objects.\n\n.. code-block:: python\n    :emphasize-lines: 10-11\n\n    class PostSchema(Schema):\n        id = fields.Str(dump_only=True)\n        title = fields.Str()\n\n        comments = fields.Relationship(\n            related_url=\"/posts/{post_id}/comments\",\n            related_url_kwargs={\"post_id\": \"<id>\"},\n            many=True,\n            include_resource_linkage=True,\n            type_=\"comments\",\n            # define a schema for rendering included data\n            schema=\"CommentSchema\",\n        )\n\n        author = fields.Relationship(\n            self_url=\"/posts/{post_id}/relationships/author\",\n            self_url_kwargs={\"post_id\": \"<id>\"},\n            related_url=\"/authors/{author_id}\",\n            related_url_kwargs={\"author_id\": \"<author.id>\"},\n            include_resource_linkage=True,\n            type_=\"users\",\n        )\n\n        class Meta:\n            type_ = \"posts\"\n\n\n    class CommentSchema(Schema):\n        id = fields.Str(dump_only=True)\n        body = fields.Str()\n\n        author = fields.Relationship(\n            self_url=\"/comments/{comment_id}/relationships/author\",\n            self_url_kwargs={\"comment_id\": \"<id>\"},\n            related_url=\"/comments/{author_id}\",\n            related_url_kwargs={\"author_id\": \"<author.id>\"},\n            type_=\"users\",\n            # define a schema for rendering included data\n            schema=\"UserSchema\",\n        )\n\n        class Meta:\n            type_ = \"comments\"\n\n\n    class UserSchema(Schema):\n        id = fields.Str(dump_only=True)\n        name = fields.Str()\n\n        class Meta:\n            type_ = \"users\"\n\nJust as with nested fields the ``schema`` can be a class or a string with a simple or fully qualified class name. Make sure to import the schema beforehand.\n\nNow you can include some data in a dump by specifying the ``include_data`` argument (also supports nested relations via the dot syntax).\n\n.. code-block:: python\n    :emphasize-lines: 8\n\n    armin = User(id=\"101\", name=\"Armin\")\n    laura = User(id=\"94\", name=\"Laura\")\n    steven = User(id=\"23\", name=\"Steven\")\n    comments = [\n        Comment(id=\"5\", body=\"Marshmallow is sweet like sugar!\", author=steven),\n        Comment(id=\"12\", body=\"Flask is Fun!\", author=armin),\n    ]\n    post = Post(id=\"1\", title=\"Django is Omakase\", author=laura, comments=comments)\n\n    PostSchema(include_data=(\"comments\", \"comments.author\")).dump(post)\n    # {\n    #     'data': {\n    #         'id': '1',\n    #         'type': 'posts',\n    #         'attributes': {'title': 'Django is Omakase'},\n    #         'relationships': {\n    #             'author': {\n    #                 'data': {'type': 'users', 'id': '94'},\n    #                 'links': {\n    #                     'self': '/posts/1/relationships/author',\n    #                     'related': '/authors/94'\n    #                 }\n    #             },\n    #             'comments': {\n    #                 'data': [\n    #                     {'type': 'comments', 'id': '5'},\n    #                     {'type': 'comments', 'id': '12'}\n    #                 ],\n    #                 'links': {\n    #                     'related': '/posts/1/comments'\n    #                 }\n    #             }\n    #         }\n    #     },\n    #     'included': [\n    #         {\n    #             'id': '5',\n    #             'type': 'comments',\n    #             'attributes': {'body': 'Marshmallow is sweet like sugar!'},\n    #             'relationships': {\n    #                 'author': {\n    #                     'data': {'type': 'users', 'id': '23'},\n    #                     'links': {\n    #                         'self': '/comments/5/relationships/author',\n    #                         'related': '/comments/23'\n    #                     }\n    #                 }\n    #             }\n    #         },\n    #         {\n    #             'id': '12',\n    #             'type': 'comments',\n    #             'attributes': {'body': 'Flask is Fun!'},\n    #             'relationships': {\n    #                 'author': {\n    #                     'data': {'type': 'users', 'id': '101'},\n    #                     'links': {\n    #                         'self': '/comments/12/relationships/author',\n    #                         'related': '/comments/101'\n    #                     }\n    #                 }\n    #             },\n    #\n    #         },\n    #         {\n    #             'id': '23',\n    #             'type': 'users',\n    #             'attributes': {'name': 'Steven'}\n    #         },\n    #         {\n    #             'id': '101',\n    #             'type': 'users',\n    #             'attributes': {'name': 'Armin'}\n    #         }\n    #     ]\n    # }\n\nMeta Information\n================\n\nThe :class:`.DocumentMeta` field is used to serialize\nthe meta object within a `document’s \"top level\" <http://jsonapi.org/format/#document-meta>`_.\n\n.. code-block:: python\n    :emphasize-lines: 6\n\n    from marshmallow_jsonapi import Schema, fields\n\n\n    class UserSchema(Schema):\n        id = fields.Str(dump_only=True)\n        name = fields.Str()\n        document_meta = fields.DocumentMeta()\n\n        class Meta:\n            type_ = \"users\"\n\n\n    user = {\"name\": \"Alice\", \"document_meta\": {\"page\": {\"offset\": 10}}}\n    UserSchema().dump(user)\n    # {\n    #     \"meta\": {\n    #         \"page\": {\n    #             \"offset\": 10\n    #         }\n    #     },\n    #     \"data\": {\n    #         \"id\": \"1\",\n    #         \"type\": \"users\"\n    #         \"attributes\": {\"name\": \"Alice\"},\n    #     }\n    # }\n\nThe :class:`.ResourceMeta` field is used to serialize the meta object within a `resource object <http://jsonapi.org/format/#document-resource-objects>`_.\n\n.. code-block:: python\n    :emphasize-lines: 6\n\n    from marshmallow_jsonapi import Schema, fields\n\n\n    class UserSchema(Schema):\n        id = fields.Str(dump_only=True)\n        name = fields.Str()\n        resource_meta = fields.ResourceMeta()\n\n        class Meta:\n            type_ = \"users\"\n\n\n    user = {\"name\": \"Alice\", \"resource_meta\": {\"active\": True}}\n    UserSchema().dump(user)\n    # {\n    #     \"data\": {\n    #         \"type\": \"users\",\n    #         \"attributes\": {\"name\": \"Alice\"},\n    #         \"meta\": {\n    #             \"active\": true\n    #         }\n    #     }\n    # }\n\nErrors\n======\n\n:func:`.Schema.load` and :func:`.Schema.validate` will return JSON API-formatted `Error objects <http://jsonapi.org/format/#error-objects>`_.\n\n.. code-block:: python\n\n    from marshmallow_jsonapi import Schema, fields\n    from marshmallow import validate, ValidationError\n\n\n    class AuthorSchema(Schema):\n        id = fields.Str(dump_only=True)\n        first_name = fields.Str(required=True)\n        last_name = fields.Str(required=True)\n        password = fields.Str(load_only=True, validate=validate.Length(6))\n        twitter = fields.Str()\n\n        class Meta:\n            type_ = \"authors\"\n\n\n    author_data = {\n        \"data\": {\"type\": \"users\", \"attributes\": {\"first_name\": \"Dan\", \"password\": \"short\"}}\n    }\n    AuthorSchema().validate(author_data)\n    # {\n    #     'errors': [\n    #         {\n    #             'detail': 'Missing data for required field.',\n    #             'source': {\n    #                 'pointer': '/data/attributes/last_name'\n    #             }\n    #         },\n    #         {\n    #             'detail': 'Shorter than minimum length 6.',\n    #             'source': {\n    #                 'pointer': '/data/attributes/password'\n    #             }\n    #         }\n    #     ]\n    # }\n\nIf an invalid \"type\" is passed in the input data, an :class:`.IncorrectTypeError` is raised.\n\n.. code-block:: python\n\n    from marshmallow_jsonapi.exceptions import IncorrectTypeError\n\n    author_data = {\n        \"data\": {\n            \"type\": \"invalid-type\",\n            \"attributes\": {\n                \"first_name\": \"Dan\",\n                \"last_name\": \"Gebhardt\",\n                \"password\": \"verysecure\",\n            },\n        }\n    }\n\n    try:\n        AuthorSchema().validate(author_data)\n    except IncorrectTypeError as err:\n        pprint(err.messages)\n    # {\n    #     'errors': [\n    #         {\n    #             'detail': 'Invalid type. Expected \"users\".',\n    #             'source': {\n    #                 'pointer': '/data/type'\n    #             }\n    #         }\n    #     ]\n    # }\n\nInflection\n==========\n\nYou can optionally specify a function to transform attribute names. For example, you may decide to follow JSON API's `recommendation <http://jsonapi.org/recommendations/#naming>`_ to use \"dasherized\" names.\n\n.. code-block:: python\n\n    from marshmallow_jsonapi import Schema, fields\n\n\n    def dasherize(text):\n        return text.replace(\"_\", \"-\")\n\n\n    class UserSchema(Schema):\n        id = fields.Str(dump_only=True)\n        first_name = fields.Str(required=True)\n        last_name = fields.Str(required=True)\n\n        class Meta:\n            type_ = \"users\"\n            inflect = dasherize\n\n\n    UserSchema().dump(user)\n    # {\n    #     'data': {\n    #         'id': '9',\n    #         'type': 'users',\n    #         'attributes': {\n    #             'first-name': 'Dan',\n    #             'last-name': 'Gebhardt'\n    #         }\n    #     }\n    # }\n\n.. _flask-integration:\n\nFlask integration\n=================\n\nmarshmallow-jsonapi includes optional utilities to integrate with Flask.\n\nA Flask-specific schema in `marshmallow_jsonapi.flask` can be used to\nauto-generate self-links based on view names instead of hard-coding URLs.\n\nAdditionally, the ``Relationship`` field in the `marshmallow_jsonapi.flask`\nmodule allows you to pass view names instead of path templates to generate\nrelationship links.\n\n.. code-block:: python\n\n    from marshmallow_jsonapi import fields\n    from marshmallow_jsonapi.flask import Relationship, Schema\n\n\n    class PostSchema(Schema):\n        id = fields.Str(dump_only=True)\n        title = fields.Str()\n\n        author = fields.Relationship(\n            self_view=\"post_author\",\n            self_url_kwargs={\"post_id\": \"<id>\"},\n            related_view=\"author_detail\",\n            related_view_kwargs={\"author_id\": \"<author.id>\"},\n        )\n\n        comments = Relationship(\n            related_view=\"post_comments\",\n            related_view_kwargs={\"post_id\": \"<id>\"},\n            many=True,\n            include_resource_linkage=True,\n            type_=\"comments\",\n        )\n\n        class Meta:\n            type_ = \"posts\"\n            self_view = \"post_detail\"\n            self_view_kwargs = {\"post_detail\": \"<id>\"}\n            self_view_many = \"posts_list\"\n\nSee `here <https://github.com/marshmallow-code/marshmallow-jsonapi/blob/dev/examples/flask_example.py>`_ for a full example.\n"
  },
  {
    "path": "docs/requirements.txt",
    "content": "marshmallow>=2.0.0rc1\nFlask==1.1.2\nsphinx==3.5.3\nsphinx-rtd-theme==0.5.0\nsphinx-issues>=0.2.0\n"
  },
  {
    "path": "examples/flask_example.py",
    "content": "from flask import Flask, request, jsonify\n\n### MODELS ###\n\n\nclass Model:\n    def __init__(self, **kwargs):\n        for key, val in kwargs.items():\n            setattr(self, key, val)\n\n\nclass Comment(Model):\n    pass\n\n\nclass Author(Model):\n    pass\n\n\nclass Post(Model):\n    pass\n\n\n### MOCK DATABASE ###\n\n\ncomment1 = Comment(id=1, body=\"First!\")\ncomment2 = Comment(id=2, body=\"I like XML better!\")\n\nauthor1 = Author(id=1, first_name=\"Dan\", last_name=\"Gebhardt\", twitter=\"dgeb\")\n\npost1 = Post(\n    id=1,\n    title=\"JSON API paints my bikeshed!\",\n    author=author1,\n    comments=[comment1, comment2],\n)\n\ndb = {\"comments\": [comment1, comment2], \"authors\": [author1], \"posts\": [post1]}\n\n\n### SCHEMAS ###\n\nfrom marshmallow import validate, ValidationError  # noqa: E402\nfrom marshmallow_jsonapi import fields  # noqa: E402\nfrom marshmallow_jsonapi.flask import Relationship, Schema  # noqa: E402\n\n\nclass CommentSchema(Schema):\n    id = fields.Str(dump_only=True)\n    body = fields.Str()\n\n    class Meta:\n        type_ = \"comments\"\n        self_view = \"comment_detail\"\n        self_view_kwargs = {\"comment_id\": \"<id>\", \"_external\": True}\n        self_view_many = \"comments_list\"\n\n\nclass AuthorSchema(Schema):\n    id = fields.Str(dump_only=True)\n    first_name = fields.Str(required=True)\n    last_name = fields.Str(required=True)\n    password = fields.Str(load_only=True, validate=validate.Length(6))\n    twitter = fields.Str()\n\n    class Meta:\n        type_ = \"people\"\n        self_view = \"author_detail\"\n        self_view_kwargs = {\"author_id\": \"<id>\"}\n        self_view_many = \"authors_list\"\n\n\nclass PostSchema(Schema):\n    id = fields.Str(dump_only=True)\n    title = fields.Str()\n\n    author = Relationship(\n        related_view=\"author_detail\",\n        related_view_kwargs={\"author_id\": \"<author.id>\", \"_external\": True},\n        include_data=True,\n        type_=\"people\",\n    )\n\n    comments = Relationship(\n        related_view=\"posts_comments\",\n        related_view_kwargs={\"post_id\": \"<id>\", \"_external\": True},\n        many=True,\n        include_data=True,\n        type_=\"comments\",\n    )\n\n    class Meta:\n        type_ = \"posts\"\n        self_view = \"posts_detail\"\n        self_view_kwargs = {\"post_id\": \"<id>\"}\n        self_view_many = \"posts_list\"\n\n\n### VIEWS ###\n\napp = Flask(__name__)\napp.config[\"DEBUG\"] = True\n\n\ndef J(*args, **kwargs):\n    \"\"\"Wrapper around jsonify that sets the Content-Type of the response to\n    application/vnd.api+json.\n    \"\"\"\n    response = jsonify(*args, **kwargs)\n    response.mimetype = \"application/vnd.api+json\"\n    return response\n\n\n@app.route(\"/posts/\", methods=[\"GET\"])\ndef posts_list():\n    posts = db[\"posts\"]\n    data = PostSchema(many=True).dump(posts)\n    return J(data)\n\n\n@app.route(\"/posts/<int:post_id>\")\ndef posts_detail(post_id):\n    post = db[\"posts\"][post_id - 1]\n    data = PostSchema().dump(post)\n    return J(data)\n\n\n@app.route(\"/posts/<int:post_id>/comments/\")\ndef posts_comments(post_id):\n    post = db[\"posts\"][post_id - 1]\n    comments = post.comments\n    data = CommentSchema(many=True).dump(comments)\n    return J(data)\n\n\n@app.route(\"/authors/\")\ndef authors_list():\n    author = db[\"authors\"]\n    data = AuthorSchema(many=True).dump(author)\n    return J(data)\n\n\n@app.route(\"/authors/<int:author_id>\")\ndef author_detail(author_id):\n    author = db[\"authors\"][author_id - 1]\n    data = AuthorSchema().dump(author)\n    return J(data)\n\n\n@app.route(\"/authors/\", methods=[\"POST\"])\ndef author_create():\n    schema = AuthorSchema()\n    input_data = request.get_json() or {}\n    try:\n        data = schema.load(input_data)\n    except ValidationError as err:\n        return J(err.messages), 422\n    id_ = len(db[\"authors\"])\n    author = Author(id=id_, **data)\n    db[\"authors\"].append(author)\n    data = schema.dump(author)\n    return J(data)\n\n\n@app.route(\"/comments/\")\ndef comments_list():\n    comment = db[\"comments\"]\n    data = CommentSchema(many=True).dump(comment)\n    return J(data)\n\n\n@app.route(\"/comments/<int:comment_id>\")\ndef comment_detail(comment_id):\n    comment = db[\"comments\"][comment_id - 1]\n    data = CommentSchema().dump(comment)\n    return J(data)\n\n\nif __name__ == \"__main__\":\n    app.run()\n"
  },
  {
    "path": "marshmallow_jsonapi/__init__.py",
    "content": "from .schema import Schema, SchemaOpts\n\n__version__ = \"0.24.0\"\n__all__ = (\"Schema\", \"SchemaOpts\")\n"
  },
  {
    "path": "marshmallow_jsonapi/exceptions.py",
    "content": "\"\"\"Exception classes.\"\"\"\n\n\nclass JSONAPIError(Exception):\n    \"\"\"Base class for all exceptions in this package.\"\"\"\n\n    pass\n\n\nclass IncorrectTypeError(JSONAPIError, ValueError):\n    \"\"\"Raised when client provides an invalid `type` in a request.\"\"\"\n\n    pointer = \"/data/type\"\n    default_message = 'Invalid type. Expected \"{expected}\".'\n\n    def __init__(self, message=None, actual=None, expected=None):\n        message = message or self.default_message\n        format_kwargs = {}\n        if actual:\n            format_kwargs[\"actual\"] = actual\n        if expected:\n            format_kwargs[\"expected\"] = expected\n        self.detail = message.format(**format_kwargs)\n        super().__init__(self.detail)\n\n    @property\n    def messages(self):\n        \"\"\"JSON API-formatted error representation.\"\"\"\n        return {\n            \"errors\": [{\"detail\": self.detail, \"source\": {\"pointer\": self.pointer}}]\n        }\n"
  },
  {
    "path": "marshmallow_jsonapi/fields.py",
    "content": "\"\"\"Includes all the fields classes from `marshmallow.fields` as well as\nfields for serializing JSON API-formatted hyperlinks.\n\"\"\"\nimport collections.abc\n\nfrom marshmallow import ValidationError, class_registry\nfrom marshmallow.fields import Field\n\n# Make core fields importable from marshmallow_jsonapi\nfrom marshmallow.fields import *  # noqa\nfrom marshmallow.base import SchemaABC\nfrom marshmallow.utils import is_collection, missing as missing_, get_value\n\nfrom .utils import resolve_params\n\n\n_RECURSIVE_NESTED = \"self\"\n# JSON API disallows U+005F LOW LINE at the start of a member name, so we can\n#  use it to load the Meta type from since it can't clash with an attribute\n# named meta (which isn't disallowed by the spec).\n_DOCUMENT_META_LOAD_FROM = \"_document_meta\"\n_RESOURCE_META_LOAD_FROM = \"_resource_meta\"\n\n\nclass BaseRelationship(Field):\n    \"\"\"Base relationship field.\n\n    This is used by `marshmallow_jsonapi.Schema` to determine which\n    fields should be formatted as relationship objects.\n\n    See: http://jsonapi.org/format/#document-resource-object-relationships\n    \"\"\"\n\n    pass\n\n\ndef _stringify(value):\n    if value is not None:\n        return str(value)\n    return value\n\n\nclass Relationship(BaseRelationship):\n    \"\"\"Framework-independent field which serializes to a \"relationship object\".\n\n    See: http://jsonapi.org/format/#document-resource-object-relationships\n\n    Examples: ::\n\n        author = Relationship(\n            related_url='/authors/{author_id}',\n            related_url_kwargs={'author_id': '<author.id>'},\n        )\n\n        comments = Relationship(\n            related_url='/posts/{post_id}/comments/',\n            related_url_kwargs={'post_id': '<id>'},\n            many=True, include_resource_linkage=True,\n            type_='comments'\n        )\n\n    This field is read-only by default.\n\n    :param str related_url: Format string for related resource links.\n    :param dict related_url_kwargs: Replacement fields for `related_url`. String arguments\n        enclosed in `< >` will be interpreted as attributes to pull from the target object.\n    :param str self_url: Format string for self relationship links.\n    :param dict self_url_kwargs: Replacement fields for `self_url`. String arguments\n        enclosed in `< >` will be interpreted as attributes to pull from the target object.\n    :param bool include_resource_linkage: Whether to include a resource linkage\n        (http://jsonapi.org/format/#document-resource-object-linkage) in the serialized result.\n    :param marshmallow_jsonapi.Schema schema: The schema to render the included data with.\n    :param bool many: Whether the relationship represents a many-to-one or many-to-many\n        relationship. Only affects serialization of the resource linkage.\n    :param str type_: The type of resource.\n    :param str id_field: Attribute name to pull ids from if a resource linkage is included.\n    \"\"\"\n\n    default_id_field = \"id\"\n\n    def __init__(\n        self,\n        related_url=\"\",\n        related_url_kwargs=None,\n        *,\n        self_url=\"\",\n        self_url_kwargs=None,\n        include_resource_linkage=False,\n        schema=None,\n        many=False,\n        type_=None,\n        id_field=None,\n        **kwargs\n    ):\n        self.related_url = related_url\n        self.related_url_kwargs = related_url_kwargs or {}\n        self.self_url = self_url\n        self.self_url_kwargs = self_url_kwargs or {}\n        if include_resource_linkage and not type_:\n            raise ValueError(\n                \"include_resource_linkage=True requires the type_ argument.\"\n            )\n        self.many = many\n        self.include_resource_linkage = include_resource_linkage\n        self.include_data = False\n        self.type_ = type_\n        self.__id_field = id_field\n        self.__schema = schema\n        super().__init__(**kwargs)\n\n    @property\n    def id_field(self):\n        if self.__id_field:\n            return self.__id_field\n        if self.__schema:\n            field = self.schema.fields[\"id\"]\n            return field.attribute or self.default_id_field\n        else:\n            return self.default_id_field\n\n    @property\n    def schema(self):\n        only = getattr(self, \"only\", None)\n        exclude = getattr(self, \"exclude\", ())\n        context = getattr(self, \"context\", {})\n\n        if isinstance(self.__schema, SchemaABC):\n            return self.__schema\n        if isinstance(self.__schema, type) and issubclass(self.__schema, SchemaABC):\n            self.__schema = self.__schema(only=only, exclude=exclude, context=context)\n            return self.__schema\n        if isinstance(self.__schema, (str, bytes)):\n            if self.__schema == _RECURSIVE_NESTED:\n                parent_class = self.parent.__class__\n                self.__schema = parent_class(\n                    only=only,\n                    exclude=exclude,\n                    context=context,\n                    include_data=self.parent.include_data,\n                )\n            else:\n                schema_class = class_registry.get_class(self.__schema)\n                self.__schema = schema_class(\n                    only=only, exclude=exclude, context=context\n                )\n            return self.__schema\n        else:\n            raise ValueError(\n                \"A Schema is required to serialize a nested \"\n                \"relationship with include_data\"\n            )\n\n    def get_related_url(self, obj):\n        if self.related_url:\n            params = resolve_params(obj, self.related_url_kwargs, default=self.default)\n            non_null_params = {\n                key: value for key, value in params.items() if value is not None\n            }\n            if non_null_params:\n                return self.related_url.format(**non_null_params)\n        return None\n\n    def get_self_url(self, obj):\n        if self.self_url:\n            params = resolve_params(obj, self.self_url_kwargs, default=self.default)\n            non_null_params = {\n                key: value for key, value in params.items() if value is not None\n            }\n            if non_null_params:\n                return self.self_url.format(**non_null_params)\n        return None\n\n    def get_resource_linkage(self, value):\n        if self.many:\n            resource_object = [\n                {\"type\": self.type_, \"id\": _stringify(self._get_id(each))}\n                for each in value\n            ]\n        else:\n            resource_object = {\n                \"type\": self.type_,\n                \"id\": _stringify(self._get_id(value)),\n            }\n        return resource_object\n\n    def extract_value(self, data):\n        \"\"\"Extract the id key and validate the request structure.\"\"\"\n        errors = []\n        if \"id\" not in data:\n            errors.append(\"Must have an `id` field\")\n        if \"type\" not in data:\n            errors.append(\"Must have a `type` field\")\n        elif data[\"type\"] != self.type_:\n            errors.append(\"Invalid `type` specified\")\n\n        if errors:\n            raise ValidationError(errors)\n\n        # If ``attributes`` is set, we've folded included data into this\n        # relationship. Unserialize it if we have a schema set; otherwise we\n        # fall back below to old behaviour of only IDs.\n        if \"attributes\" in data and self.__schema:\n            result = self.schema.load(\n                {\"data\": data, \"included\": self.root.included_data}\n            )\n            return result\n\n        id_value = data.get(\"id\")\n\n        if self.__schema:\n            id_value = self.schema.fields[\"id\"].deserialize(id_value)\n\n        return id_value\n\n    def deserialize(self, value, attr=None, data=None, **kwargs):\n        \"\"\"Deserialize ``value``.\n\n        :raise ValidationError: If the value is not type `dict`, if the\n            value does not contain a `data` key, and if the value is\n            required but unspecified.\n        \"\"\"\n        if value is missing_:\n            return super().deserialize(value, attr, data)\n        if not isinstance(value, dict) or \"data\" not in value:\n            # a relationships object does not need 'data' if 'links' is present\n            if value and \"links\" in value:\n                return missing_\n            else:\n                raise ValidationError(\"Must include a `data` key\")\n        return super().deserialize(value[\"data\"], attr, data, **kwargs)\n\n    def _deserialize(self, value, attr, obj, **kwargs):\n        if self.many:\n            if not is_collection(value):\n                raise ValidationError(\"Relationship is list-like\")\n            return [self.extract_value(item) for item in value]\n\n        if is_collection(value):\n            raise ValidationError(\"Relationship is not list-like\")\n        return self.extract_value(value)\n\n    # We have to override serialize because we don't want those fields\n    # to be serialized which are related to the resource but not included\n    # in the request. And we don't have enough control in _serialize\n    # to prevent their serialization\n    def serialize(self, attr, obj, accessor=None):\n        if obj is None or self.include_resource_linkage or self.include_data:\n            return super().serialize(attr, obj, accessor)\n        return self._serialize(None, attr, obj)\n\n    def _serialize(self, value, attr, obj):\n        dict_class = self.parent.dict_class if self.parent else dict\n\n        ret = dict_class()\n        self_url = self.get_self_url(obj)\n        related_url = self.get_related_url(obj)\n        if self_url or related_url:\n            ret[\"links\"] = dict_class()\n            if self_url:\n                ret[\"links\"][\"self\"] = self_url\n            if related_url:\n                ret[\"links\"][\"related\"] = related_url\n\n        # resource linkage is required when including the data\n        if self.include_resource_linkage or self.include_data:\n            if value is None:\n                ret[\"data\"] = [] if self.many else None\n            else:\n                ret[\"data\"] = self.get_resource_linkage(value)\n\n        if self.include_data and value is not None:\n            if self.many:\n                for item in value:\n                    self._serialize_included(item)\n            else:\n                self._serialize_included(value)\n        return ret\n\n    def _serialize_included(self, value):\n        result = self.schema.dump(value)\n        item = result[\"data\"]\n        self.root.included_data[(item[\"type\"], item[\"id\"])] = item\n        for key, value in self.schema.included_data.items():\n            self.root.included_data[key] = value\n\n    def _get_id(self, value):\n        if self.__schema:\n            return self.schema.get_attribute(value, self.id_field, value)\n        else:\n            return get_value(value, self.id_field, value)\n\n\nclass DocumentMeta(Field):\n    \"\"\"Field which serializes to a \"meta object\" within a document’s “top level”.\n\n    Examples: ::\n\n        from marshmallow_jsonapi import Schema, fields\n\n        class UserSchema(Schema):\n            id = fields.String()\n            metadata = fields.DocumentMeta()\n\n            class Meta:\n                type_ = 'product'\n\n    See: http://jsonapi.org/format/#document-meta\n    \"\"\"\n\n    default_error_messages = {\"invalid\": \"Not a valid mapping type.\"}\n\n    def __init__(self, **kwargs):\n        super().__init__(**kwargs)\n        self.data_key = _DOCUMENT_META_LOAD_FROM\n\n    def _deserialize(self, value, attr, data, **kwargs):\n        if isinstance(value, collections.abc.Mapping):\n            return value\n        else:\n            raise self.make_error(\"invalid\")\n\n    def _serialize(self, value, *args, **kwargs):\n        if isinstance(value, collections.abc.Mapping):\n            return super()._serialize(value, *args, **kwargs)\n        else:\n            raise self.make_error(\"invalid\")\n\n\nclass ResourceMeta(Field):\n    \"\"\"Field which serializes to a \"meta object\" within a \"resource object\".\n\n    Examples: ::\n\n        from marshmallow_jsonapi import Schema, fields\n\n        class UserSchema(Schema):\n            id = fields.String()\n            meta_resource = fields.ResourceMeta()\n\n            class Meta:\n                type_ = 'product'\n\n    See: http://jsonapi.org/format/#document-resource-objects\n    \"\"\"\n\n    default_error_messages = {\"invalid\": \"Not a valid mapping type.\"}\n\n    def __init__(self, **kwargs):\n        super().__init__(**kwargs)\n        self.data_key = _RESOURCE_META_LOAD_FROM\n\n    def _deserialize(self, value, attr, data, **kwargs):\n        if isinstance(value, collections.abc.Mapping):\n            return value\n        else:\n            raise self.make_error(\"invalid\")\n\n    def _serialize(self, value, *args, **kwargs):\n        if isinstance(value, collections.abc.Mapping):\n            return super()._serialize(value, *args, **kwargs)\n        else:\n            raise self.make_error(\"invalid\")\n"
  },
  {
    "path": "marshmallow_jsonapi/flask.py",
    "content": "\"\"\"Flask integration that avoids the need to hard-code URLs for links.\n\nThis includes a Flask-specific schema with custom Meta options and a\nrelationship field for linking to related resources.\n\"\"\"\nimport flask\nfrom werkzeug.routing import BuildError\n\nfrom .fields import Relationship as GenericRelationship\nfrom .schema import Schema as DefaultSchema, SchemaOpts as DefaultOpts\nfrom .utils import resolve_params\n\n\nclass SchemaOpts(DefaultOpts):\n    \"\"\"Options to use Flask view names instead of hard coding URLs.\"\"\"\n\n    def __init__(self, meta, *args, **kwargs):\n        if getattr(meta, \"self_url\", None):\n            raise ValueError(\n                \"Use `self_view` instead of `self_url` \" \"using the Flask extension.\"\n            )\n        if getattr(meta, \"self_url_kwargs\", None):\n            raise ValueError(\n                \"Use `self_view_kwargs` instead of `self_url_kwargs` \"\n                \"when using the Flask extension.\"\n            )\n        if getattr(meta, \"self_url_many\", None):\n            raise ValueError(\n                \"Use `self_view_many` instead of `self_url_many` \"\n                \"when using the Flask extension.\"\n            )\n\n        if getattr(meta, \"self_view_kwargs\", None) and not getattr(\n            meta, \"self_view\", None\n        ):\n            raise ValueError(\n                \"Must specify `self_view` Meta option when \"\n                \"`self_view_kwargs` is specified.\"\n            )\n\n        # Transfer Flask options to URL options, to piggy-back on its handling\n        meta.self_url = getattr(meta, \"self_view\", None)\n        meta.self_url_kwargs = getattr(meta, \"self_view_kwargs\", None)\n        meta.self_url_many = getattr(meta, \"self_view_many\", None)\n\n        super().__init__(meta, *args, **kwargs)\n\n\nclass Schema(DefaultSchema):\n    \"\"\"A Flask specific schema that resolves self URLs from view names.\"\"\"\n\n    OPTIONS_CLASS = SchemaOpts\n\n    class Meta:\n        \"\"\"Options object that takes the same options as `marshmallow-jsonapi.Schema`,\n        but instead of ``self_url``, ``self_url_kwargs`` and ``self_url_many``\n        has the following options to resolve the URLs from Flask views:\n\n        * ``self_view`` - View name to resolve the self URL link from.\n        * ``self_view_kwargs`` - Replacement fields for ``self_view``. String\n          attributes enclosed in ``< >`` will be interpreted as attributes to\n          pull from the schema data.\n        * ``self_view_many`` - View name to resolve the self URL link when a\n          collection of resources is returned.\n        \"\"\"\n\n        pass\n\n    def generate_url(self, view_name, **kwargs):\n        \"\"\"Generate URL with any kwargs interpolated.\"\"\"\n        return flask.url_for(view_name, **kwargs) if view_name else None\n\n\nclass Relationship(GenericRelationship):\n    r\"\"\"Field which serializes to a \"relationship object\"\n    with a \"related resource link\".\n\n    See: http://jsonapi.org/format/#document-resource-object-relationships\n\n    Examples: ::\n\n        author = Relationship(\n            related_view='author_detail',\n            related_view_kwargs={'author_id': '<author.id>'},\n        )\n\n        comments = Relationship(\n            related_view='posts_comments',\n            related_view_kwargs={'post_id': '<id>'},\n            many=True, include_resource_linkage=True,\n            type_='comments'\n        )\n\n    This field is read-only by default.\n\n    :param str related_view: View name for related resource link.\n    :param dict related_view_kwargs: Path kwargs fields for `related_view`. String arguments\n        enclosed in `< >` will be interpreted as attributes to pull from the target object.\n    :param str self_view: View name for self relationship link.\n    :param dict self_view_kwargs: Path kwargs for `self_view`. String arguments\n        enclosed in `< >` will be interpreted as attributes to pull from the target object.\n    :param \\*\\*kwargs: Same keyword arguments as `marshmallow_jsonapi.fields.Relationship`.\n    \"\"\"\n\n    def __init__(\n        self,\n        related_view=None,\n        related_view_kwargs=None,\n        *,\n        self_view=None,\n        self_view_kwargs=None,\n        **kwargs\n    ):\n        self.related_view = related_view\n        self.related_view_kwargs = related_view_kwargs or {}\n        self.self_view = self_view\n        self.self_view_kwargs = self_view_kwargs or {}\n        super().__init__(**kwargs)\n\n    def get_url(self, obj, view_name, view_kwargs):\n        if view_name:\n            kwargs = resolve_params(obj, view_kwargs, default=self.default)\n            kwargs[\"endpoint\"] = view_name\n            try:\n                return flask.url_for(**kwargs)\n            except BuildError:\n                if (\n                    None in kwargs.values()\n                ):  # most likely to be caused by empty relationship\n                    return None\n                raise\n        return None\n\n    def get_related_url(self, obj):\n        return self.get_url(obj, self.related_view, self.related_view_kwargs)\n\n    def get_self_url(self, obj):\n        return self.get_url(obj, self.self_view, self.self_view_kwargs)\n"
  },
  {
    "path": "marshmallow_jsonapi/schema.py",
    "content": "import itertools\n\nimport marshmallow as ma\nfrom marshmallow.exceptions import ValidationError\nfrom marshmallow.utils import is_collection\n\nfrom .fields import BaseRelationship, DocumentMeta, ResourceMeta\nfrom .fields import _RESOURCE_META_LOAD_FROM, _DOCUMENT_META_LOAD_FROM\nfrom .exceptions import IncorrectTypeError\nfrom .utils import resolve_params\n\nTYPE = \"type\"\nID = \"id\"\n\n\nclass SchemaOpts(ma.SchemaOpts):\n    def __init__(self, meta, *args, **kwargs):\n        super().__init__(meta, *args, **kwargs)\n        self.type_ = getattr(meta, \"type_\", None)\n        self.inflect = getattr(meta, \"inflect\", None)\n        self.self_url = getattr(meta, \"self_url\", None)\n        self.self_url_kwargs = getattr(meta, \"self_url_kwargs\", None)\n        self.self_url_many = getattr(meta, \"self_url_many\", None)\n\n\nclass Schema(ma.Schema):\n    \"\"\"Schema class that formats data according to JSON API 1.0.\n    Must define the ``type_`` `class Meta` option.\n\n    Example: ::\n\n        from marshmallow_jsonapi import Schema, fields\n\n        def dasherize(text):\n            return text.replace('_', '-')\n\n        class PostSchema(Schema):\n            id = fields.Str(dump_only=True)  # Required\n            title = fields.Str()\n\n            author = fields.HyperlinkRelated(\n                '/authors/{author_id}',\n                url_kwargs={'author_id': '<author.id>'},\n            )\n\n            comments = fields.HyperlinkRelated(\n                '/posts/{post_id}/comments',\n                url_kwargs={'post_id': '<id>'},\n                # Include resource linkage\n                many=True, include_resource_linkage=True,\n                type_='comments'\n            )\n\n            class Meta:\n                type_ = 'posts'  # Required\n                inflect = dasherize\n\n    \"\"\"\n\n    class Meta:\n        \"\"\"Options object for `Schema`. Takes the same options as `marshmallow.Schema.Meta` with\n        the addition of:\n\n        * ``type_`` - required, the JSON API resource type as a string.\n        * ``inflect`` - optional, an inflection function to modify attribute names.\n        * ``self_url`` - optional, URL to use to `self` in links\n        * ``self_url_kwargs`` - optional, replacement fields for `self_url`.\n          String arguments enclosed in ``< >`` will be interpreted as attributes\n          to pull from the schema data.\n        * ``self_url_many`` - optional, URL to use to `self` in top-level ``links``\n          when a collection of resources is returned.\n        \"\"\"\n\n        pass\n\n    def __init__(self, *args, **kwargs):\n        self.include_data = kwargs.pop(\"include_data\", ())\n        super().__init__(*args, **kwargs)\n        if self.include_data:\n            self.check_relations(self.include_data)\n\n        if not self.opts.type_:\n            raise ValueError(\"Must specify type_ class Meta option\")\n\n        if \"id\" not in self.fields:\n            raise ValueError(\"Must have an `id` field\")\n\n        if self.opts.self_url_kwargs and not self.opts.self_url:\n            raise ValueError(\n                \"Must specify `self_url` Meta option when \"\n                \"`self_url_kwargs` is specified\"\n            )\n        self.included_data = {}\n        self.document_meta = {}\n\n    OPTIONS_CLASS = SchemaOpts\n\n    def check_relations(self, relations):\n        \"\"\"Recursive function which checks if a relation is valid.\"\"\"\n        for rel in relations:\n            if not rel:\n                continue\n            fields = rel.split(\".\", 1)\n\n            local_field = fields[0]\n            if local_field not in self.fields:\n                raise ValueError(f'Unknown field \"{local_field}\"')\n\n            field = self.fields[local_field]\n            if not isinstance(field, BaseRelationship):\n                raise ValueError(\n                    'Can only include relationships. \"{}\" is a \"{}\"'.format(\n                        field.name, field.__class__.__name__\n                    )\n                )\n\n            field.include_data = True\n            if len(fields) > 1:\n                field.schema.check_relations(fields[1:])\n\n    @ma.post_dump(pass_many=True)\n    def format_json_api_response(self, data, many, **kwargs):\n        \"\"\"Post-dump hook that formats serialized data as a top-level JSON API object.\n\n        See: http://jsonapi.org/format/#document-top-level\n        \"\"\"\n        ret = self.format_items(data, many)\n        ret = self.wrap_response(ret, many)\n        ret = self.render_included_data(ret)\n        ret = self.render_meta_document(ret)\n        return ret\n\n    def render_included_data(self, data):\n        if not self.included_data:\n            return data\n        data[\"included\"] = list(self.included_data.values())\n        return data\n\n    def render_meta_document(self, data):\n        if not self.document_meta:\n            return data\n        data[\"meta\"] = self.document_meta\n        return data\n\n    def unwrap_item(self, item):\n        if \"type\" not in item:\n            raise ma.ValidationError(\n                [\n                    {\n                        \"detail\": \"`data` object must include `type` key.\",\n                        \"source\": {\"pointer\": \"/data\"},\n                    }\n                ]\n            )\n        if item[\"type\"] != self.opts.type_:\n            raise IncorrectTypeError(actual=item[\"type\"], expected=self.opts.type_)\n\n        payload = self.dict_class()\n        if \"id\" in item:\n            payload[\"id\"] = item[\"id\"]\n        if \"meta\" in item:\n            payload[_RESOURCE_META_LOAD_FROM] = item[\"meta\"]\n        if self.document_meta:\n            payload[_DOCUMENT_META_LOAD_FROM] = self.document_meta\n        for key, value in item.get(\"attributes\", {}).items():\n            payload[key] = value\n        for key, value in item.get(\"relationships\", {}).items():\n            # Fold included data related to this relationship into the item, so\n            # that we can deserialize the whole objects instead of just IDs.\n            if self.included_data:\n                included_data = []\n                inner_data = value.get(\"data\", [])\n\n                # Data may be ``None`` (for empty relationships), but we only\n                # need to process it when it's present.\n                if inner_data:\n                    if not is_collection(inner_data):\n                        included_data = next(\n                            self._extract_from_included(inner_data), None\n                        )\n                    else:\n                        for data in inner_data:\n                            included_data.extend(self._extract_from_included(data))\n\n                if included_data:\n                    value[\"data\"] = included_data\n\n            payload[key] = value\n        return payload\n\n    @ma.pre_load(pass_many=True)\n    def unwrap_request(self, data, many, **kwargs):\n        if \"data\" not in data:\n            raise ma.ValidationError(\n                [\n                    {\n                        \"detail\": \"Object must include `data` key.\",\n                        \"source\": {\"pointer\": \"/\"},\n                    }\n                ]\n            )\n\n        data = data[\"data\"]\n        if many:\n            if not is_collection(data):\n                raise ma.ValidationError(\n                    [\n                        {\n                            \"detail\": \"`data` expected to be a collection.\",\n                            \"source\": {\"pointer\": \"/data\"},\n                        }\n                    ]\n                )\n            return [self.unwrap_item(each) for each in data]\n        return self.unwrap_item(data)\n\n    def on_bind_field(self, field_name, field_obj):\n        \"\"\"Schema hook override. When binding fields, set ``data_key`` to the inflected form of field_name.\"\"\"\n        if not field_obj.data_key:\n            field_obj.data_key = self.inflect(field_name)\n        return None\n\n    def _do_load(self, data, many=None, **kwargs):\n        \"\"\"Override `marshmallow.Schema._do_load` for custom JSON API handling.\n\n        Specifically, we do this to format errors as JSON API Error objects,\n        and to support loading of included data.\n        \"\"\"\n        many = self.many if many is None else bool(many)\n\n        # Store this on the instance so we have access to the included data\n        # when processing relationships (``included`` is outside of the\n        # ``data``).\n        self.included_data = data.get(\"included\", {})\n        self.document_meta = data.get(\"meta\", {})\n\n        try:\n            result = super()._do_load(data, many=many, **kwargs)\n        except ValidationError as err:  # strict mode\n            error_messages = err.messages\n            if \"_schema\" in error_messages:\n                error_messages = error_messages[\"_schema\"]\n            formatted_messages = self.format_errors(error_messages, many=many)\n            err.messages = formatted_messages\n            raise err\n        return result\n\n    def _extract_from_included(self, data):\n        \"\"\"Extract included data matching the items in ``data``.\n\n        For each item in ``data``, extract the full data from the included\n        data.\n        \"\"\"\n        return (\n            item\n            for item in self.included_data\n            if item[\"type\"] == data[\"type\"] and str(item[\"id\"]) == str(data[\"id\"])\n        )\n\n    def inflect(self, text):\n        \"\"\"Inflect ``text`` if the ``inflect`` class Meta option is defined, otherwise\n        do nothing.\n        \"\"\"\n        return self.opts.inflect(text) if self.opts.inflect else text\n\n    ### Overridable hooks ###\n\n    def format_errors(self, errors, many):\n        \"\"\"Format validation errors as JSON Error objects.\"\"\"\n        if not errors:\n            return {}\n        if isinstance(errors, (list, tuple)):\n            return {\"errors\": errors}\n\n        formatted_errors = []\n        if many:\n            for index, i_errors in errors.items():\n                formatted_errors.extend(self._get_formatted_errors(i_errors, index))\n        else:\n            formatted_errors.extend(self._get_formatted_errors(errors))\n\n        return {\"errors\": formatted_errors}\n\n    def _get_formatted_errors(self, errors, index=None):\n        return itertools.chain(\n            *(\n                [\n                    self.format_error(field_name, message, index=index)\n                    for message in field_errors\n                ]\n                for field_name, field_errors in itertools.chain(\n                    *(self._process_nested_errors(k, v) for k, v in errors.items())\n                )\n            )\n        )\n\n    def _process_nested_errors(self, name, data):\n        if not isinstance(data, dict):\n            return [(name, data)]\n\n        return itertools.chain(\n            *(self._process_nested_errors(f\"{name}/{k}\", v) for k, v in data.items())\n        )\n\n    def format_error(self, field_name, message, index=None):\n        \"\"\"Override-able hook to format a single error message as an Error object.\n\n        See: http://jsonapi.org/format/#error-objects\n        \"\"\"\n        pointer = [\"/data\"]\n\n        if index is not None:\n            pointer.append(str(index))\n\n        relationship = isinstance(\n            self.declared_fields.get(field_name), BaseRelationship\n        )\n        if relationship:\n            pointer.append(\"relationships\")\n        elif field_name != \"id\":\n            # JSONAPI identifier is a special field that exists above the attribute object.\n            pointer.append(\"attributes\")\n\n        pointer.append(self.inflect(field_name))\n\n        if relationship:\n            pointer.append(\"data\")\n\n        return {\"detail\": message, \"source\": {\"pointer\": \"/\".join(pointer)}}\n\n    def format_item(self, item):\n        \"\"\"Format a single datum as a Resource object.\n\n        See: http://jsonapi.org/format/#document-resource-objects\n        \"\"\"\n        # http://jsonapi.org/format/#document-top-level\n        # Primary data MUST be either... a single resource object, a single resource\n        # identifier object, or null, for requests that target single resources\n        if not item:\n            return None\n\n        ret = self.dict_class()\n        ret[TYPE] = self.opts.type_\n\n        # Get the schema attributes so we can confirm `dump-to` values exist\n        attributes = {\n            (self.fields[field].data_key or field): field for field in self.fields\n        }\n\n        for field_name, value in item.items():\n            attribute = attributes[field_name]\n            if attribute == ID:\n                ret[ID] = value\n            elif isinstance(self.fields[attribute], DocumentMeta):\n                if not self.document_meta:\n                    self.document_meta = self.dict_class()\n                self.document_meta.update(value)\n            elif isinstance(self.fields[attribute], ResourceMeta):\n                if \"meta\" not in ret:\n                    ret[\"meta\"] = self.dict_class()\n                ret[\"meta\"].update(value)\n            elif isinstance(self.fields[attribute], BaseRelationship):\n                if value:\n                    if \"relationships\" not in ret:\n                        ret[\"relationships\"] = self.dict_class()\n                    ret[\"relationships\"][self.inflect(field_name)] = value\n            else:\n                if \"attributes\" not in ret:\n                    ret[\"attributes\"] = self.dict_class()\n                ret[\"attributes\"][self.inflect(field_name)] = value\n\n        links = self.get_resource_links(item)\n        if links:\n            ret[\"links\"] = links\n        return ret\n\n    def format_items(self, data, many):\n        \"\"\"Format data as a Resource object or list of Resource objects.\n\n        See: http://jsonapi.org/format/#document-resource-objects\n        \"\"\"\n        if many:\n            return [self.format_item(item) for item in data]\n        else:\n            return self.format_item(data)\n\n    def get_top_level_links(self, data, many):\n        \"\"\"Hook for adding links to the root of the response data.\"\"\"\n        self_link = None\n\n        if many:\n            if self.opts.self_url_many:\n                self_link = self.generate_url(self.opts.self_url_many)\n        else:\n            if self.opts.self_url:\n                self_link = data.get(\"links\", {}).get(\"self\", None)\n\n        return {\"self\": self_link}\n\n    def get_resource_links(self, item):\n        \"\"\"Hook for adding links to a resource object.\"\"\"\n        if self.opts.self_url:\n            ret = self.dict_class()\n            kwargs = resolve_params(item, self.opts.self_url_kwargs or {})\n            ret[\"self\"] = self.generate_url(self.opts.self_url, **kwargs)\n            return ret\n        return None\n\n    def wrap_response(self, data, many):\n        \"\"\"Wrap data and links according to the JSON API\"\"\"\n        ret = {\"data\": data}\n        # self_url_many is still valid when there isn't any data, but self_url\n        # may only be included if there is data in the ret\n        if many or data:\n            top_level_links = self.get_top_level_links(data, many)\n            if top_level_links[\"self\"]:\n                ret[\"links\"] = top_level_links\n        return ret\n\n    def generate_url(self, link, **kwargs):\n        \"\"\"Generate URL with any kwargs interpolated.\"\"\"\n        return link.format_map(kwargs) if link else None\n"
  },
  {
    "path": "marshmallow_jsonapi/utils.py",
    "content": "\"\"\"Utility functions.\n\nThis module should be considered private API.\n\"\"\"\nimport re\n\nfrom marshmallow.utils import get_value, missing\n\n\n_tpl_pattern = re.compile(r\"\\s*<\\s*(\\S*)\\s*>\\s*\")\n\n\ndef tpl(val):\n    \"\"\"Return value within ``< >`` if possible, else return ``None``.\"\"\"\n    match = _tpl_pattern.match(val)\n    if match:\n        return match.groups()[0]\n    return None\n\n\ndef resolve_params(obj, params, default=missing):\n    \"\"\"Given a dictionary of keyword arguments, return the same dictionary except with\n    values enclosed in `< >` resolved to attributes on `obj`.\n    \"\"\"\n    param_values = {}\n    for name, attr_tpl in params.items():\n        attr_name = tpl(str(attr_tpl))\n        if attr_name:\n            attribute_value = get_value(obj, attr_name, default=default)\n            if attribute_value is not missing:\n                param_values[name] = attribute_value\n            else:\n                raise AttributeError(\n                    \"{attr_name!r} is not a valid \"\n                    \"attribute of {obj!r}\".format(attr_name=attr_name, obj=obj)\n                )\n        else:\n            param_values[name] = attr_tpl\n    return param_values\n"
  },
  {
    "path": "setup.cfg",
    "content": "[metadata]\nlicense_files = LICENSE\n\n[bdist_wheel]\n# This flag says that the code is written to work on both Python 2 and Python\n# 3. If at all possible, it is good practice to do this. If you cannot, you\n# will need to generate wheels for each Python version that you support.\nuniversal=1\n\n[flake8]\nignore = E203, E266, E501, W503\nmax-line-length = 110\nmax-complexity = 18\nselect = B,C,E,F,W,T4,B9\n"
  },
  {
    "path": "setup.py",
    "content": "import re\nfrom setuptools import setup, find_packages\n\nINSTALL_REQUIRES = (\"marshmallow>=2.15.2\",)\nEXTRAS_REQUIRE = {\n    \"tests\": [\"pytest\", \"mock\", \"faker==4.18.0\", \"Flask==1.1.2\"],\n    \"lint\": [\"flake8==3.9.0\", \"flake8-bugbear==20.11.1\", \"pre-commit~=2.0\"],\n}\nEXTRAS_REQUIRE[\"dev\"] = EXTRAS_REQUIRE[\"tests\"] + EXTRAS_REQUIRE[\"lint\"] + [\"tox\"]\n\n\ndef find_version(fname):\n    \"\"\"Attempts to find the version number in the file names fname.\n    Raises RuntimeError if not found.\n    \"\"\"\n    version = \"\"\n    with open(fname) as fp:\n        reg = re.compile(r'__version__ = [\\'\"]([^\\'\"]*)[\\'\"]')\n        for line in fp:\n            m = reg.match(line)\n            if m:\n                version = m.group(1)\n                break\n    if not version:\n        raise RuntimeError(\"Cannot find version information\")\n    return version\n\n\ndef read(fname):\n    with open(fname) as fp:\n        content = fp.read()\n    return content\n\n\nsetup(\n    name=\"marshmallow-jsonapi\",\n    version=find_version(\"marshmallow_jsonapi/__init__.py\"),\n    description=\"JSON API 1.0 (https://jsonapi.org) formatting with marshmallow\",\n    long_description=read(\"README.rst\"),\n    author=\"Steven Loria\",\n    author_email=\"sloria1@gmail.com\",\n    url=\"https://github.com/marshmallow-code/marshmallow-jsonapi\",\n    packages=find_packages(exclude=(\"test*\",)),\n    package_dir={\"marshmallow-jsonapi\": \"marshmallow-jsonapi\"},\n    include_package_data=True,\n    install_requires=INSTALL_REQUIRES,\n    extras_require=EXTRAS_REQUIRE,\n    python_requires=\">=3.6\",\n    license=\"MIT\",\n    zip_safe=False,\n    keywords=(\n        \"marshmallow-jsonapi marshmallow marshalling serialization \"\n        \"jsonapi deserialization validation\"\n    ),\n    classifiers=[\n        \"Intended Audience :: Developers\",\n        \"License :: OSI Approved :: MIT License\",\n        \"Natural Language :: English\",\n        \"Programming Language :: Python :: 3\",\n        \"Programming Language :: Python :: 3.6\",\n        \"Programming Language :: Python :: 3.7\",\n        \"Programming Language :: Python :: 3.8\",\n        \"Programming Language :: Python :: 3.9\",\n    ],\n    test_suite=\"tests\",\n    project_urls={\n        \"Bug Reports\": \"https://github.com/marshmallow-code/marshmallow-jsonapi/issues\",\n        \"Funding\": \"https://opencollective.com/marshmallow\",\n    },\n)\n"
  },
  {
    "path": "tests/__init__.py",
    "content": ""
  },
  {
    "path": "tests/base.py",
    "content": "from hashlib import md5\n\nfrom faker import Factory\nfrom marshmallow import validate\n\nfrom marshmallow_jsonapi import Schema, fields\n\nfake = Factory.create()\n\n\nclass Bunch:\n    def __init__(self, **kwargs):\n        for key, val in kwargs.items():\n            setattr(self, key, val)\n\n\nclass Post(Bunch):\n    pass\n\n\nclass Author(Bunch):\n    pass\n\n\nclass Comment(Bunch):\n    pass\n\n\nclass Keyword(Bunch):\n    pass\n\n\nclass AuthorSchema(Schema):\n    id = fields.Str()\n    first_name = fields.Str(required=True)\n    last_name = fields.Str(required=True)\n    password = fields.Str(load_only=True, validate=validate.Length(6))\n    twitter = fields.Str()\n\n    def get_top_level_links(self, data, many):\n        if many:\n            self_link = \"/authors/\"\n        else:\n            self_link = \"/authors/{}\".format(data[\"id\"])\n        return {\"self\": self_link}\n\n    class Meta:\n        type_ = \"people\"\n\n\nclass KeywordSchema(Schema):\n    id = fields.Str()\n    keyword = fields.Str(required=True)\n\n    def get_attribute(self, attr, obj, default):\n        if obj == \"id\":\n            return md5(\n                super(Schema, self)\n                .get_attribute(attr, \"keyword\", default)\n                .encode(\"utf-8\")\n            ).hexdigest()\n        else:\n            return super(Schema, self).get_attribute(attr, obj, default)\n\n    class Meta:\n        type_ = \"keywords\"\n        strict = True\n\n\nclass CommentSchema(Schema):\n    id = fields.Str()\n    body = fields.Str(required=True)\n\n    author = fields.Relationship(\n        \"http://test.test/comments/{id}/author/\",\n        related_url_kwargs={\"id\": \"<id>\"},\n        schema=AuthorSchema,\n        many=False,\n    )\n\n    class Meta:\n        type_ = \"comments\"\n        strict = True\n\n\nclass ArticleSchema(Schema):\n    id = fields.Integer()\n    body = fields.String()\n    author = fields.Relationship(\n        dump_only=False, include_resource_linkage=True, many=False, type_=\"people\"\n    )\n    comments = fields.Relationship(\n        dump_only=False, include_resource_linkage=True, many=True, type_=\"comments\"\n    )\n\n    class Meta:\n        type_ = \"articles\"\n        strict = True\n\n\nclass PostSchema(Schema):\n    id = fields.Str()\n    post_title = fields.Str(attribute=\"title\", dump_to=\"title\", data_key=\"title\")\n\n    author = fields.Relationship(\n        \"http://test.test/posts/{id}/author/\",\n        related_url_kwargs={\"id\": \"<id>\"},\n        schema=AuthorSchema,\n        many=False,\n        type_=\"people\",\n    )\n\n    post_comments = fields.Relationship(\n        \"http://test.test/posts/{id}/comments/\",\n        related_url_kwargs={\"id\": \"<id>\"},\n        attribute=\"comments\",\n        load_from=\"post-comments\",\n        dump_to=\"post-comments\",\n        data_key=\"post-comments\",\n        schema=\"CommentSchema\",\n        many=True,\n        type_=\"comments\",\n    )\n\n    post_keywords = fields.Relationship(\n        \"http://test.test/posts/{id}/keywords/\",\n        related_url_kwargs={\"id\": \"<id>\"},\n        attribute=\"keywords\",\n        dump_to=\"post-keywords\",\n        data_key=\"post-keywords\",\n        schema=\"KeywordSchema\",\n        many=True,\n        type_=\"keywords\",\n    )\n\n    class Meta:\n        type_ = \"posts\"\n        strict = True\n\n\nclass PolygonSchema(Schema):\n    id = fields.Integer(as_string=True)\n    sides = fields.Integer()\n    # This is an attribute that uses the 'meta' key: /data/attributes/meta\n    meta = fields.String()\n    # This is the document's top level meta object: /meta\n    document_meta = fields.DocumentMeta()\n    # This is the resource object's meta object: /data/meta\n    resource_meta = fields.ResourceMeta()\n\n    class Meta:\n        type_ = \"shapes\"\n        strict = True\n"
  },
  {
    "path": "tests/conftest.py",
    "content": "import pytest\n\nfrom tests.base import Author, Post, Comment, Keyword, fake\n\n\ndef make_author():\n    return Author(\n        id=fake.random_int(),\n        first_name=fake.first_name(),\n        last_name=fake.last_name(),\n        twitter=fake.domain_word(),\n    )\n\n\ndef make_post(with_comments=True, with_author=True, with_keywords=True):\n    comments = [make_comment() for _ in range(2)] if with_comments else []\n    keywords = [make_keyword() for _ in range(3)] if with_keywords else []\n    author = make_author() if with_author else None\n    return Post(\n        id=fake.random_int(),\n        title=fake.catch_phrase(),\n        author=author,\n        author_id=author.id if with_author else None,\n        comments=comments,\n        keywords=keywords,\n    )\n\n\ndef make_comment(with_author=True):\n    author = make_author() if with_author else None\n    return Comment(id=fake.random_int(), body=fake.bs(), author=author)\n\n\ndef make_keyword():\n    return Keyword(keyword=fake.domain_word())\n\n\n@pytest.fixture()\ndef author():\n    return make_author()\n\n\n@pytest.fixture()\ndef authors():\n    return [make_author() for _ in range(3)]\n\n\n@pytest.fixture()\ndef comments():\n    return [make_comment() for _ in range(3)]\n\n\n@pytest.fixture()\ndef post():\n    return make_post()\n\n\n@pytest.fixture()\ndef post_with_null_comment():\n    return make_post(with_comments=False)\n\n\n@pytest.fixture()\ndef post_with_null_author():\n    return make_post(with_author=False)\n\n\n@pytest.fixture()\ndef posts():\n    return [make_post() for _ in range(3)]\n"
  },
  {
    "path": "tests/test_fields.py",
    "content": "import pytest\n\nfrom hashlib import md5\nfrom marshmallow import ValidationError, missing as missing_\nfrom marshmallow.fields import Int\n\nfrom marshmallow_jsonapi import Schema\nfrom marshmallow_jsonapi.fields import Str, DocumentMeta, ResourceMeta, Relationship\n\n\nclass TestGenericRelationshipField:\n    def test_serialize_relationship_link(self, post):\n        field = Relationship(\n            \"http://example.com/posts/{id}/comments\", related_url_kwargs={\"id\": \"<id>\"}\n        )\n        result = field.serialize(\"comments\", post)\n        assert field.serialize(\"comments\", post)\n        related = result[\"links\"][\"related\"]\n        assert related == f\"http://example.com/posts/{post.id}/comments\"\n\n    def test_serialize_self_link(self, post):\n        field = Relationship(\n            self_url=\"http://example.com/posts/{id}/relationships/comments\",\n            self_url_kwargs={\"id\": \"<id>\"},\n        )\n        result = field.serialize(\"comments\", post)\n        related = result[\"links\"][\"self\"]\n        assert \"related\" not in result[\"links\"]\n        assert related == \"http://example.com/posts/{id}/relationships/comments\".format(\n            id=post.id\n        )\n\n    def test_include_resource_linkage_requires_type(self):\n        with pytest.raises(ValueError) as excinfo:\n            Relationship(\n                related_url=\"/posts/{post_id}\",\n                related_url_kwargs={\"post_id\": \"<id>\"},\n                include_resource_linkage=True,\n            )\n        assert (\n            excinfo.value.args[0]\n            == \"include_resource_linkage=True requires the type_ argument.\"\n        )\n\n    def test_include_resource_linkage_single(self, post):\n        field = Relationship(\n            related_url=\"/posts/{post_id}/author/\",\n            related_url_kwargs={\"post_id\": \"<id>\"},\n            include_resource_linkage=True,\n            type_=\"people\",\n        )\n        result = field.serialize(\"author\", post)\n        assert \"data\" in result\n        assert result[\"data\"]\n        assert result[\"data\"][\"id\"] == str(post.author.id)\n\n    def test_include_resource_linkage_single_with_schema(self, post):\n        field = Relationship(\n            related_url=\"/posts/{post_id}/author/\",\n            related_url_kwargs={\"post_id\": \"<id>\"},\n            include_resource_linkage=True,\n            type_=\"people\",\n            schema=\"PostSchema\",\n        )\n        result = field.serialize(\"author\", post)\n        assert \"data\" in result\n        assert result[\"data\"]\n        assert result[\"data\"][\"id\"] == str(post.author.id)\n\n    def test_include_resource_linkage_single_foreign_key(self, post):\n        field = Relationship(\n            related_url=\"/posts/{post_id}/author/\",\n            related_url_kwargs={\"post_id\": \"<id>\"},\n            include_resource_linkage=True,\n            type_=\"people\",\n        )\n        result = field.serialize(\"author_id\", post)\n        assert result[\"data\"][\"id\"] == str(post.author_id)\n\n    def test_include_resource_linkage_single_foreign_key_with_schema(self, post):\n        field = Relationship(\n            related_url=\"/posts/{post_id}/author/\",\n            related_url_kwargs={\"post_id\": \"<id>\"},\n            include_resource_linkage=True,\n            type_=\"people\",\n            schema=\"PostSchema\",\n        )\n        result = field.serialize(\"author_id\", post)\n        assert result[\"data\"][\"id\"] == str(post.author_id)\n\n    def test_include_resource_linkage_id_field_from_string(self):\n        field = Relationship(\n            include_resource_linkage=True, type_=\"authors\", id_field=\"name\"\n        )\n        result = field.serialize(\"author\", {\"author\": {\"name\": \"Ray Bradbury\"}})\n        assert \"data\" in result\n        assert result[\"data\"]\n        assert result[\"data\"][\"id\"] == \"Ray Bradbury\"\n\n    def test_include_resource_linkage_id_field_from_schema(self):\n        class AuthorSchema(Schema):\n            id = Str(attribute=\"name\")\n\n            class Meta:\n                type_ = \"authors\"\n                strict = True\n\n        field = Relationship(\n            include_resource_linkage=True, type_=\"authors\", schema=AuthorSchema\n        )\n        result = field.serialize(\"author\", {\"author\": {\"name\": \"Ray Bradbury\"}})\n        assert \"data\" in result\n        assert result[\"data\"]\n        assert result[\"data\"][\"id\"] == \"Ray Bradbury\"\n\n    def test_include_resource_linkage_many(self, post):\n        field = Relationship(\n            related_url=\"/posts/{post_id}/comments\",\n            related_url_kwargs={\"post_id\": \"<id>\"},\n            many=True,\n            include_resource_linkage=True,\n            type_=\"comments\",\n        )\n        result = field.serialize(\"comments\", post)\n        assert \"data\" in result\n        ids = [each[\"id\"] for each in result[\"data\"]]\n        assert ids == [str(each.id) for each in post.comments]\n\n    def test_include_resource_linkage_many_with_schema(self, post):\n        field = Relationship(\n            related_url=\"/posts/{post_id}/comments\",\n            related_url_kwargs={\"post_id\": \"<id>\"},\n            many=True,\n            include_resource_linkage=True,\n            type_=\"comments\",\n            schema=\"CommentSchema\",\n        )\n        result = field.serialize(\"comments\", post)\n        assert \"data\" in result\n        ids = [each[\"id\"] for each in result[\"data\"]]\n        assert ids == [str(each.id) for each in post.comments]\n\n    def test_include_resource_linkage_many_with_schema_overriding_get_attribute(\n        self, post\n    ):\n        field = Relationship(\n            related_url=\"/posts/{post_id}/keywords\",\n            related_url_kwargs={\"post_id\": \"<id>\"},\n            many=True,\n            include_resource_linkage=True,\n            type_=\"keywords\",\n            schema=\"KeywordSchema\",\n        )\n        result = field.serialize(\"keywords\", post)\n        assert \"data\" in result\n        ids = [each[\"id\"] for each in result[\"data\"]]\n        assert ids == [\n            md5(each.keyword.encode(\"utf-8\")).hexdigest() for each in post.keywords\n        ]\n\n    def test_deserialize_data_single(self):\n        field = Relationship(\n            related_url=\"/posts/{post_id}/comments\",\n            related_url_kwargs={\"post_id\": \"<id>\"},\n            many=False,\n            include_resource_linkage=True,\n            type_=\"comments\",\n        )\n        value = {\"data\": {\"type\": \"comments\", \"id\": \"1\"}}\n        result = field.deserialize(value)\n        assert result == \"1\"\n\n    def test_deserialize_data_many(self):\n        field = Relationship(\n            related_url=\"/posts/{post_id}/comments\",\n            related_url_kwargs={\"post_id\": \"<id>\"},\n            many=True,\n            include_resource_linkage=True,\n            type_=\"comments\",\n        )\n        value = {\"data\": [{\"type\": \"comments\", \"id\": \"1\"}]}\n        result = field.deserialize(value)\n        assert result == [\"1\"]\n\n    def test_deserialize_data_missing_id(self):\n        field = Relationship(\n            related_url=\"/posts/{post_id}/comments\",\n            related_url_kwargs={\"post_id\": \"<id>\"},\n            many=False,\n            include_resource_linkage=True,\n            type_=\"comments\",\n        )\n        with pytest.raises(ValidationError) as excinfo:\n            value = {\"data\": {\"type\": \"comments\"}}\n            field.deserialize(value)\n        assert excinfo.value.args[0] == [\"Must have an `id` field\"]\n\n    def test_deserialize_data_missing_type(self):\n        field = Relationship(\n            related_url=\"/posts/{post_id}/comments\",\n            related_url_kwargs={\"post_id\": \"<id>\"},\n            many=False,\n            include_resource_linkage=True,\n            type_=\"comments\",\n        )\n        with pytest.raises(ValidationError) as excinfo:\n            value = {\"data\": {\"id\": \"1\"}}\n            field.deserialize(value)\n        assert excinfo.value.args[0] == [\"Must have a `type` field\"]\n\n    def test_deserialize_data_incorrect_type(self):\n        field = Relationship(\n            related_url=\"/posts/{post_id}/comments\",\n            related_url_kwargs={\"post_id\": \"<id>\"},\n            many=False,\n            include_resource_linkage=True,\n            type_=\"comments\",\n        )\n        with pytest.raises(ValidationError) as excinfo:\n            value = {\"data\": {\"type\": \"posts\", \"id\": \"1\"}}\n            field.deserialize(value)\n        assert excinfo.value.args[0] == [\"Invalid `type` specified\"]\n\n    def test_deserialize_null_data_value(self):\n        field = Relationship(\n            related_url=\"/posts/{post_id}/comments\",\n            related_url_kwargs={\"post_id\": \"<id>\"},\n            allow_none=True,\n            many=False,\n            include_resource_linkage=False,\n            type_=\"comments\",\n        )\n        result = field.deserialize({\"data\": None})\n        assert result is None\n\n    def test_deserialize_null_value_disallow_none(self):\n        field = Relationship(\n            related_url=\"/posts/{post_id}/comments\",\n            related_url_kwargs={\"post_id\": \"<id>\"},\n            allow_none=False,\n            many=False,\n            include_resource_linkage=False,\n            type_=\"comments\",\n        )\n        with pytest.raises(ValidationError) as excinfo:\n            field.deserialize({\"data\": None})\n        assert excinfo.value.args[0] == \"Field may not be null.\"\n\n    def test_deserialize_empty_data_list(self):\n        field = Relationship(\n            related_url=\"/posts/{post_id}/comments\",\n            related_url_kwargs={\"post_id\": \"<id>\"},\n            many=True,\n            include_resource_linkage=False,\n            type_=\"comments\",\n        )\n        result = field.deserialize({\"data\": []})\n        assert result == []\n\n    def test_deserialize_empty_data(self):\n        field = Relationship(\n            related_url=\"/posts/{post_id}/comments\",\n            related_url_kwargs={\"post_id\": \"<id>\"},\n            many=False,\n            include_resource_linkage=False,\n            type_=\"comments\",\n        )\n        with pytest.raises(ValidationError) as excinfo:\n            field.deserialize({\"data\": {}})\n        assert excinfo.value.args[0] == [\n            \"Must have an `id` field\",\n            \"Must have a `type` field\",\n        ]\n\n    def test_deserialize_required_missing(self):\n        field = Relationship(\n            related_url=\"/posts/{post_id}/comments\",\n            related_url_kwargs={\"post_id\": \"<id>\"},\n            required=True,\n            many=False,\n            include_resource_linkage=True,\n            type_=\"comments\",\n        )\n        with pytest.raises(ValidationError) as excinfo:\n            field.deserialize(missing_)\n        assert excinfo.value.args[0] == \"Missing data for required field.\"\n\n    def test_deserialize_required_empty(self):\n        field = Relationship(\n            related_url=\"/posts/{post_id}/comments\",\n            related_url_kwargs={\"post_id\": \"<id>\"},\n            required=True,\n            many=False,\n            include_resource_linkage=False,\n            type_=\"comments\",\n        )\n        with pytest.raises(ValidationError) as excinfo:\n            field.deserialize({})\n        assert excinfo.value.args[0] == \"Must include a `data` key\"\n\n    def test_deserialize_many_non_list_relationship(self):\n        field = Relationship(many=True, include_resource_linkage=True, type_=\"comments\")\n        with pytest.raises(ValidationError) as excinfo:\n            field.deserialize({\"data\": \"1\"})\n        assert excinfo.value.args[0] == \"Relationship is list-like\"\n\n    def test_deserialize_non_many_list_relationship(self):\n        field = Relationship(\n            many=False, include_resource_linkage=True, type_=\"comments\"\n        )\n        with pytest.raises(ValidationError) as excinfo:\n            field.deserialize({\"data\": [\"1\"]})\n        assert excinfo.value.args[0] == \"Relationship is not list-like\"\n\n    def test_include_null_data_single(self, post_with_null_author):\n        field = Relationship(\n            related_url=\"posts/{post_id}/author\",\n            related_url_kwargs={\"post_id\": \"<id>\"},\n            include_resource_linkage=True,\n            type_=\"people\",\n        )\n        result = field.serialize(\"author\", post_with_null_author)\n        assert result and result[\"links\"][\"related\"]\n        assert result[\"data\"] is None\n\n    def test_include_null_data_many(self, post_with_null_comment):\n        field = Relationship(\n            related_url=\"/posts/{post_id}/comments\",\n            related_url_kwargs={\"post_id\": \"<id>\"},\n            many=True,\n            include_resource_linkage=True,\n            type_=\"comments\",\n        )\n        result = field.serialize(\"comments\", post_with_null_comment)\n        assert result and result[\"links\"][\"related\"]\n        assert result[\"data\"] == []\n\n    def test_exclude_data(self, post_with_null_comment):\n        field = Relationship(\n            related_url=\"/posts/{post_id}/comments\",\n            related_url_kwargs={\"post_id\": \"<id>\"},\n            many=True,\n            include_resource_linkage=False,\n            type_=\"comments\",\n        )\n        result = field.serialize(\"comments\", post_with_null_comment)\n        assert result and result[\"links\"][\"related\"]\n        assert \"data\" not in result\n\n    def test_empty_relationship_with_alternative_identifier_field(\n        self, post_with_null_author\n    ):\n        field = Relationship(\n            related_url=\"/authors/{author_id}\",\n            related_url_kwargs={\"author_id\": \"<author.last_name>\"},\n            default=None,\n        )\n        result = field.serialize(\"author\", post_with_null_author)\n\n        assert not result\n\n    def test_resource_linkage_id_type_from_schema(self):\n        class AuthorSchema(Schema):\n            id = Int(attribute=\"author_id\", as_string=True)\n\n            class Meta:\n                type_ = \"authors\"\n                strict = True\n\n        field = Relationship(\n            include_resource_linkage=True, type_=\"authors\", schema=AuthorSchema\n        )\n\n        result = field.deserialize({\"data\": {\"type\": \"authors\", \"id\": \"1\"}})\n\n        assert result == 1\n\n    def test_resource_linkage_id_of_invalid_type(self):\n        class AuthorSchema(Schema):\n            id = Int(attribute=\"author_id\", as_string=True)\n\n            class Meta:\n                type_ = \"authors\"\n                strict = True\n\n        field = Relationship(\n            include_resource_linkage=True, type_=\"authors\", schema=AuthorSchema\n        )\n\n        with pytest.raises(ValidationError) as excinfo:\n            field.deserialize({\"data\": {\"type\": \"authors\", \"id\": \"not_a_number\"}})\n        assert excinfo.value.args[0] == \"Not a valid integer.\"\n\n\nclass TestDocumentMetaField:\n    def test_serialize(self):\n        field = DocumentMeta()\n        result = field.serialize(\n            \"document_meta\", {\"document_meta\": {\"page\": {\"offset\": 1}}}\n        )\n        assert result == {\"page\": {\"offset\": 1}}\n\n    def test_serialize_incorrect_type(self):\n        field = DocumentMeta()\n        with pytest.raises(ValidationError) as excinfo:\n            field.serialize(\"document_meta\", {\"document_meta\": 1})\n        assert excinfo.value.args[0] == \"Not a valid mapping type.\"\n\n    def test_deserialize(self):\n        field = DocumentMeta()\n        value = {\"page\": {\"offset\": 1}}\n        result = field.deserialize(value)\n        assert result == value\n\n    def test_deserialize_incorrect_type(self):\n        field = DocumentMeta()\n        value = 1\n        with pytest.raises(ValidationError) as excinfo:\n            field.deserialize(value)\n        assert excinfo.value.args[0] == \"Not a valid mapping type.\"\n\n\nclass TestResourceMetaField:\n    def test_serialize(self):\n        field = ResourceMeta()\n        result = field.serialize(\"resource_meta\", {\"resource_meta\": {\"active\": True}})\n        assert result == {\"active\": True}\n\n    def test_serialize_incorrect_type(self):\n        field = ResourceMeta()\n        with pytest.raises(ValidationError) as excinfo:\n            field.serialize(\"resource_meta\", {\"resource_meta\": True})\n        assert excinfo.value.args[0] == \"Not a valid mapping type.\"\n\n    def test_deserialize(self):\n        field = ResourceMeta()\n        value = {\"active\": True}\n        result = field.deserialize(value)\n        assert result == value\n\n    def test_deserialize_incorrect_type(self):\n        field = ResourceMeta()\n        value = True\n        with pytest.raises(ValidationError) as excinfo:\n            field.deserialize(value)\n        assert excinfo.value.args[0] == \"Not a valid mapping type.\"\n"
  },
  {
    "path": "tests/test_flask.py",
    "content": "from flask import Flask, url_for\nimport pytest\nfrom werkzeug.routing import BuildError\n\nfrom marshmallow_jsonapi import fields\nfrom marshmallow_jsonapi.flask import Relationship, Schema\n\n\n@pytest.fixture()\ndef app():\n    app_ = Flask(\"testapp\")\n    app_.config[\"DEBUG\"] = True\n    app_.config[\"TESTING\"] = True\n\n    @app_.route(\"/posts/\")\n    def posts():\n        return \"All posts\"\n\n    @app_.route(\"/posts/<post_id>/\")\n    def post_detail(post_id):\n        return f\"Detail for post {post_id}\"\n\n    @app_.route(\"/posts/<post_id>/comments/\")\n    def posts_comments(post_id):\n        return f\"Comments for post {post_id}\"\n\n    @app_.route(\"/authors/<int:author_id>\")\n    def author_detail(author_id):\n        return f\"Detail for author {author_id}\"\n\n    ctx = app_.test_request_context()\n    ctx.push()\n    yield app_\n    ctx.pop()\n\n\nclass TestSchema:\n    class PostFlaskSchema(Schema):\n        id = fields.Int()\n        title = fields.Str()\n\n        class Meta:\n            type_ = \"posts\"\n            self_view = \"post_detail\"\n            self_view_kwargs = {\"post_id\": \"<id>\"}\n            self_view_many = \"posts\"\n\n    class PostAuthorFlaskSchema(Schema):\n        id = fields.Int()\n        title = fields.Str()\n\n        field = Relationship(\n            related_view=\"author_detail\",\n            related_view_kwargs={\"author_id\": \"<author.last_name>\"},\n            default=None,\n        )\n\n        class Meta:\n            type_ = \"posts\"\n            self_view = \"post_detail\"\n            self_view_kwargs = {\"post_id\": \"<id>\"}\n            self_view_many = \"posts\"\n\n    def test_schema_requires_view_options(self):\n        with pytest.raises(ValueError):\n\n            class InvalidFlaskMetaSchema(Schema):\n                id = fields.Int()\n\n                class Meta:\n                    type_ = \"posts\"\n                    self_url = \"/posts/{id}\"\n                    self_url_kwargs = {\"post_id\": \"<id>\"}\n\n    def test_non_existing_view(self, app, post):\n        class InvalidFlaskMetaSchema(Schema):\n            id = fields.Int()\n\n            class Meta:\n                type_ = \"posts\"\n                self_view = \"wrong_view\"\n                self_view_kwargs = {\"post_id\": \"<id>\"}\n\n        with pytest.raises(BuildError):\n            InvalidFlaskMetaSchema().dump(post)\n\n    def test_self_link_single(self, app, post):\n        data = self.PostFlaskSchema().dump(post)\n        assert \"links\" in data\n        assert data[\"links\"][\"self\"] == f\"/posts/{post.id}/\"\n\n    def test_self_link_many(self, app, posts):\n        data = self.PostFlaskSchema(many=True).dump(posts)\n        assert \"links\" in data\n        assert data[\"links\"][\"self\"] == \"/posts/\"\n\n        assert \"links\" in data[\"data\"][0]\n        assert data[\"data\"][0][\"links\"][\"self\"] == f\"/posts/{posts[0].id}/\"\n\n    def test_schema_with_empty_relationship(self, app, post_with_null_author):\n        data = self.PostAuthorFlaskSchema().dump(post_with_null_author)\n        assert \"relationships\" not in data\n\n\nclass TestRelationshipField:\n    def test_serialize_basic(self, app, post):\n        field = Relationship(\n            related_view=\"posts_comments\", related_view_kwargs={\"post_id\": \"<id>\"}\n        )\n        result = field.serialize(\"comments\", post)\n        assert \"links\" in result\n        assert \"related\" in result[\"links\"]\n        related = result[\"links\"][\"related\"]\n        assert related == url_for(\"posts_comments\", post_id=post.id)\n\n    def test_serialize_external(self, app, post):\n        field = Relationship(\n            related_view=\"posts_comments\",\n            related_view_kwargs={\"post_id\": \"<id>\", \"_external\": True},\n        )\n        result = field.serialize(\"comments\", post)\n        related = result[\"links\"][\"related\"]\n        assert related == url_for(\"posts_comments\", post_id=post.id, _external=True)\n\n    def test_include_resource_linkage_requires_type(self, app, post):\n        with pytest.raises(ValueError) as excinfo:\n            Relationship(\n                related_view=\"posts_comments\",\n                related_view_kwargs={\"post_id\": \"<id>\"},\n                include_resource_linkage=True,\n            )\n        assert (\n            excinfo.value.args[0]\n            == \"include_resource_linkage=True requires the type_ argument.\"\n        )\n\n    def test_serialize_self_link(self, app, post):\n        field = Relationship(\n            self_view=\"posts_comments\", self_view_kwargs={\"post_id\": \"<id>\"}\n        )\n        result = field.serialize(\"comments\", post)\n        assert \"links\" in result\n        assert \"self\" in result[\"links\"]\n        related = result[\"links\"][\"self\"]\n        assert related == url_for(\"posts_comments\", post_id=post.id)\n\n    def test_empty_relationship(self, app, post_with_null_author):\n        field = Relationship(\n            related_view=\"author_detail\", related_view_kwargs={\"author_id\": \"<author>\"}\n        )\n        result = field.serialize(\"author\", post_with_null_author)\n\n        assert not result\n\n    def test_non_existing_view(self, app, post):\n        field = Relationship(\n            related_view=\"non_existing_view\",\n            related_view_kwargs={\"author_id\": \"<author>\"},\n        )\n        with pytest.raises(BuildError):\n            field.serialize(\"author\", post)\n\n    def test_empty_relationship_with_alternative_identifier_field(\n        self, app, post_with_null_author\n    ):\n        field = Relationship(\n            related_view=\"author_detail\",\n            related_view_kwargs={\"author_id\": \"<author.last_name>\"},\n            default=None,\n        )\n        result = field.serialize(\"author\", post_with_null_author)\n\n        assert not result\n"
  },
  {
    "path": "tests/test_options.py",
    "content": "import pytest\nfrom marshmallow import validate, ValidationError\n\nfrom marshmallow_jsonapi import Schema, fields\nfrom tests.base import AuthorSchema, CommentSchema\nfrom tests.test_schema import make_serialized_author, get_error_by_field\n\n\ndef dasherize(text):\n    return text.replace(\"_\", \"-\")\n\n\nclass AuthorSchemaWithInflection(Schema):\n    id = fields.Int(dump_only=True)\n    first_name = fields.Str(required=True, validate=validate.Length(min=2))\n    last_name = fields.Str(required=True)\n\n    class Meta:\n        type_ = \"people\"\n        inflect = dasherize\n        strict = True\n\n\nclass AuthorSchemaWithOverrideInflection(Schema):\n    id = fields.Str(dump_only=True)\n    # data_key and load_from takes precedence over inflected attribute\n    first_name = fields.Str(data_key=\"firstName\", load_from=\"firstName\")\n    last_name = fields.Str()\n\n    class Meta:\n        type_ = \"people\"\n        inflect = dasherize\n        strict = True\n\n\nclass TestInflection:\n    @pytest.fixture()\n    def schema(self):\n        return AuthorSchemaWithInflection()\n\n    def test_dump(self, schema, author):\n        data = schema.dump(author)\n\n        assert data[\"data\"][\"id\"] == author.id\n        assert data[\"data\"][\"type\"] == \"people\"\n        attribs = data[\"data\"][\"attributes\"]\n\n        assert \"first-name\" in attribs\n        assert \"last-name\" in attribs\n\n        assert attribs[\"first-name\"] == author.first_name\n        assert attribs[\"last-name\"] == author.last_name\n\n    def test_validate_with_inflection(self, schema):\n        errors = schema.validate(make_serialized_author({\"first-name\": \"d\"}))\n        lname_err = get_error_by_field(errors, \"last-name\")\n        assert lname_err\n        assert lname_err[\"detail\"] == \"Missing data for required field.\"\n\n        fname_err = get_error_by_field(errors, \"first-name\")\n        assert fname_err\n        assert fname_err[\"detail\"] == \"Shorter than minimum length 2.\"\n\n    def test_load_with_inflection(self, schema):\n        # invalid\n        with pytest.raises(ValidationError) as excinfo:\n            schema.load(make_serialized_author({\"first-name\": \"d\"}))\n        errors = excinfo.value.messages\n        fname_err = get_error_by_field(errors, \"first-name\")\n        assert fname_err\n        assert fname_err[\"detail\"] == \"Shorter than minimum length 2.\"\n\n        # valid\n        data = schema.load(\n            make_serialized_author({\"first-name\": \"Nevets\", \"last-name\": \"Longoria\"})\n        )\n\n        assert data[\"first_name\"] == \"Nevets\"\n\n    def test_load_with_inflection_and_load_from_override(self):\n        schema = AuthorSchemaWithOverrideInflection()\n        data = schema.load(\n            make_serialized_author({\"firstName\": \"Steve\", \"last-name\": \"Loria\"})\n        )\n\n        assert data[\"first_name\"] == \"Steve\"\n        assert data[\"last_name\"] == \"Loria\"\n\n    def test_load_bulk_id_fields(self):\n        request = {\"data\": [{\"id\": \"1\", \"type\": \"people\"}]}\n\n        result = AuthorSchema(only=(\"id\",), many=True).load(request)\n        assert type(result) is list\n\n        response = result[0]\n        assert response[\"id\"] == request[\"data\"][0][\"id\"]\n\n    def test_relationship_keys_get_inflected(self, post):\n        class PostSchema(Schema):\n            id = fields.Int()\n            post_title = fields.Str(attribute=\"title\")\n\n            post_comments = fields.Relationship(\n                \"http://test.test/posts/{id}/comments/\",\n                related_url_kwargs={\"id\": \"<id>\"},\n                attribute=\"comments\",\n            )\n\n            class Meta:\n                type_ = \"posts\"\n                inflect = dasherize\n                strict = True\n\n        data = PostSchema().dump(post)\n        assert \"post-title\" in data[\"data\"][\"attributes\"]\n        assert \"post-comments\" in data[\"data\"][\"relationships\"]\n        related_href = data[\"data\"][\"relationships\"][\"post-comments\"][\"links\"][\n            \"related\"\n        ]\n        assert related_href == f\"http://test.test/posts/{post.id}/comments/\"\n\n\nclass AuthorAutoSelfLinkSchema(Schema):\n    id = fields.Int(dump_only=True)\n    first_name = fields.Str(required=True)\n    last_name = fields.Str(required=True)\n    password = fields.Str(load_only=True, validate=validate.Length(6))\n    twitter = fields.Str()\n\n    class Meta:\n        type_ = \"people\"\n        self_url = \"/authors/{id}\"\n        self_url_kwargs = {\"id\": \"<id>\"}\n        self_url_many = \"/authors/\"\n\n\nclass AuthorAutoSelfLinkFirstLastSchema(AuthorAutoSelfLinkSchema):\n    class Meta:\n        type_ = \"people\"\n        self_url = \"http://example.com/authors/{first_name} {last_name}\"\n        self_url_kwargs = {\"first_name\": \"<first_name>\", \"last_name\": \"<last_name>\"}\n        self_url_many = \"http://example.com/authors/\"\n\n\nclass TestAutoSelfUrls:\n    def test_self_url_kwargs_requires_self_url(self, author):\n        class InvalidSelfLinkSchema(Schema):\n            id = fields.Int()\n\n            class Meta:\n                type_ = \"people\"\n                self_url_kwargs = {\"id\": \"<id>\"}\n\n        with pytest.raises(ValueError):\n            InvalidSelfLinkSchema().dump(author)\n\n    def test_self_link_single(self, author):\n        data = AuthorAutoSelfLinkSchema().dump(author)\n        assert \"links\" in data\n        assert data[\"links\"][\"self\"] == f\"/authors/{author.id}\"\n\n    def test_self_link_many(self, authors):\n        data = AuthorAutoSelfLinkSchema(many=True).dump(authors)\n        assert \"links\" in data\n        assert data[\"links\"][\"self\"] == \"/authors/\"\n\n        assert \"links\" in data[\"data\"][0]\n        assert data[\"data\"][0][\"links\"][\"self\"] == f\"/authors/{authors[0].id}\"\n\n    def test_without_self_link(self, comments):\n        data = CommentSchema(many=True).dump(comments)\n\n        assert \"data\" in data\n        assert type(data[\"data\"]) is list\n\n        first = data[\"data\"][0]\n        assert first[\"id\"] == str(comments[0].id)\n        assert first[\"type\"] == \"comments\"\n\n        assert \"links\" not in data\n"
  },
  {
    "path": "tests/test_schema.py",
    "content": "import pytest\nimport marshmallow as ma\nfrom marshmallow import ValidationError, INCLUDE\n\nfrom marshmallow_jsonapi import Schema, fields\nfrom marshmallow_jsonapi.exceptions import IncorrectTypeError\nfrom tests.base import (\n    AuthorSchema,\n    CommentSchema,\n    PostSchema,\n    PolygonSchema,\n    ArticleSchema,\n)\n\n\ndef make_serialized_author(attributes):\n    return {\"data\": {\"type\": \"people\", \"attributes\": attributes}}\n\n\ndef make_serialized_authors(items):\n    return {\"data\": [{\"type\": \"people\", \"attributes\": each} for each in items]}\n\n\ndef test_type_is_required():\n    class BadSchema(Schema):\n        id = fields.Str()\n\n        class Meta:\n            pass\n\n    with pytest.raises(ValueError) as excinfo:\n        BadSchema()\n    assert excinfo.value.args[0] == \"Must specify type_ class Meta option\"\n\n\ndef test_id_field_is_required():\n    class BadSchema(Schema):\n        class Meta:\n            type_ = \"users\"\n\n    with pytest.raises(ValueError) as excinfo:\n        BadSchema()\n    assert excinfo.value.args[0] == \"Must have an `id` field\"\n\n\nclass TestResponseFormatting:\n    def test_dump_single(self, author):\n        data = AuthorSchema().dump(author)\n\n        assert \"data\" in data\n        assert type(data[\"data\"]) is dict\n\n        assert data[\"data\"][\"id\"] == str(author.id)\n        assert data[\"data\"][\"type\"] == \"people\"\n        attribs = data[\"data\"][\"attributes\"]\n\n        assert attribs[\"first_name\"] == author.first_name\n        assert attribs[\"last_name\"] == author.last_name\n\n    def test_dump_many(self, authors):\n        data = AuthorSchema(many=True).dump(authors)\n        assert \"data\" in data\n        assert type(data[\"data\"]) is list\n\n        first = data[\"data\"][0]\n        assert first[\"id\"] == str(authors[0].id)\n        assert first[\"type\"] == \"people\"\n\n        attribs = first[\"attributes\"]\n\n        assert attribs[\"first_name\"] == authors[0].first_name\n        assert attribs[\"last_name\"] == authors[0].last_name\n\n    def test_self_link_single(self, author):\n        data = AuthorSchema().dump(author)\n        assert \"links\" in data\n        assert data[\"links\"][\"self\"] == f\"/authors/{author.id}\"\n\n    def test_self_link_many(self, authors):\n        data = AuthorSchema(many=True).dump(authors)\n        assert \"links\" in data\n        assert data[\"links\"][\"self\"] == \"/authors/\"\n\n    def test_dump_to(self, post):\n        data = PostSchema().dump(post)\n        assert \"data\" in data\n        assert \"attributes\" in data[\"data\"]\n        assert \"title\" in data[\"data\"][\"attributes\"]\n        assert \"relationships\" in data[\"data\"]\n        assert \"post-comments\" in data[\"data\"][\"relationships\"]\n\n    def test_dump_none(self):\n        data = AuthorSchema().dump(None)\n\n        assert \"data\" in data\n        assert data[\"data\"] is None\n        assert \"links\" not in data\n\n    def test_schema_with_relationship_processes_none(self):\n        data = CommentSchema().dump(None)\n        assert data == {\"data\": None}\n\n    def test_dump_empty_list(self):\n        data = AuthorSchema(many=True).dump([])\n\n        assert \"data\" in data\n        assert type(data[\"data\"]) is list\n        assert len(data[\"data\"]) == 0\n        assert \"links\" in data\n        assert data[\"links\"][\"self\"] == \"/authors/\"\n\n\nclass TestCompoundDocuments:\n    def test_include_data_with_many(self, post):\n        data = PostSchema(include_data=(\"post_comments\", \"post_comments.author\")).dump(\n            post\n        )\n\n        assert \"included\" in data\n        assert len(data[\"included\"]) == 4\n        first_comment = [i for i in data[\"included\"] if i[\"type\"] == \"comments\"][0]\n        assert \"attributes\" in first_comment\n        assert \"body\" in first_comment[\"attributes\"]\n\n    def test_include_data_with_single(self, post):\n        data = PostSchema(include_data=(\"author\",)).dump(post)\n        assert \"included\" in data\n        assert len(data[\"included\"]) == 1\n        author = data[\"included\"][0]\n        assert \"attributes\" in author\n        assert \"first_name\" in author[\"attributes\"]\n\n    def test_include_data_with_all_relations(self, post):\n        data = PostSchema(\n            include_data=(\"author\", \"post_comments\", \"post_comments.author\")\n        ).dump(post)\n\n        assert \"included\" in data\n        assert len(data[\"included\"]) == 5\n        for included in data[\"included\"]:\n            assert included[\"id\"]\n            assert included[\"type\"] in (\"people\", \"comments\")\n        expected_comments_author_ids = {\n            str(comment.author.id) for comment in post.comments\n        }\n        included_comments_author_ids = {\n            i[\"id\"]\n            for i in data[\"included\"]\n            if i[\"type\"] == \"people\" and i[\"id\"] != str(post.author.id)\n        }\n        assert included_comments_author_ids == expected_comments_author_ids\n\n    def test_include_no_data(self, post):\n        data = PostSchema(include_data=()).dump(post)\n        assert \"included\" not in data\n\n    def test_include_self_referential_relationship(self):\n        class RefSchema(Schema):\n            id = fields.Int()\n            data = fields.Str()\n            parent = fields.Relationship(schema=\"self\", many=False)\n\n            class Meta:\n                type_ = \"refs\"\n\n        obj = {\"id\": 1, \"data\": \"data1\", \"parent\": {\"id\": 2, \"data\": \"data2\"}}\n        data = RefSchema(include_data=(\"parent\",)).dump(obj)\n        assert \"included\" in data\n        assert data[\"included\"][0][\"attributes\"][\"data\"] == \"data2\"\n\n    def test_include_self_referential_relationship_many(self):\n        class RefSchema(Schema):\n            id = fields.Str()\n            data = fields.Str()\n            children = fields.Relationship(schema=\"self\", many=True)\n\n            class Meta:\n                type_ = \"refs\"\n\n        obj = {\n            \"id\": \"1\",\n            \"data\": \"data1\",\n            \"children\": [{\"id\": \"2\", \"data\": \"data2\"}, {\"id\": \"3\", \"data\": \"data3\"}],\n        }\n        data = RefSchema(include_data=(\"children\",)).dump(obj)\n        assert \"included\" in data\n        assert len(data[\"included\"]) == 2\n        for child in data[\"included\"]:\n            assert child[\"attributes\"][\"data\"] == \"data%s\" % child[\"id\"]\n\n    def test_include_self_referential_relationship_many_deep(self):\n        class RefSchema(Schema):\n            id = fields.Str()\n            data = fields.Str()\n            children = fields.Relationship(schema=\"self\", type_=\"refs\", many=True)\n\n            class Meta:\n                type_ = \"refs\"\n\n        obj = {\n            \"id\": \"1\",\n            \"data\": \"data1\",\n            \"children\": [\n                {\"id\": \"2\", \"data\": \"data2\", \"children\": []},\n                {\n                    \"id\": \"3\",\n                    \"data\": \"data3\",\n                    \"children\": [\n                        {\"id\": \"4\", \"data\": \"data4\", \"children\": []},\n                        {\"id\": \"5\", \"data\": \"data5\", \"children\": []},\n                    ],\n                },\n            ],\n        }\n        data = RefSchema(include_data=(\"children\",)).dump(obj)\n        assert \"included\" in data\n        assert len(data[\"included\"]) == 4\n        for child in data[\"included\"]:\n            assert child[\"attributes\"][\"data\"] == \"data%s\" % child[\"id\"]\n\n    def test_include_data_with_many_and_schema_as_class(self, post):\n        class PostClassSchema(PostSchema):\n            post_comments = fields.Relationship(\n                \"http://test.test/posts/{id}/comments/\",\n                related_url_kwargs={\"id\": \"<id>\"},\n                attribute=\"comments\",\n                dump_to=\"post-comments\",\n                schema=CommentSchema,\n                many=True,\n            )\n\n            class Meta(PostSchema.Meta):\n                pass\n\n        data = PostClassSchema(include_data=(\"post_comments\",)).dump(post)\n        assert \"included\" in data\n        assert len(data[\"included\"]) == 2\n        first_comment = data[\"included\"][0]\n        assert \"attributes\" in first_comment\n        assert \"body\" in first_comment[\"attributes\"]\n\n    def test_include_data_with_nested_only_arg(self, post):\n        data = PostSchema(\n            only=(\n                \"id\",\n                \"post_comments.id\",\n                \"post_comments.author.id\",\n                \"post_comments.author.twitter\",\n            ),\n            include_data=(\"post_comments\", \"post_comments.author\"),\n        ).dump(post)\n\n        assert \"included\" in data\n        assert len(data[\"included\"]) == 4\n\n        first_author = [i for i in data[\"included\"] if i[\"type\"] == \"people\"][0]\n        assert \"twitter\" in first_author[\"attributes\"]\n        for attribute in (\"first_name\", \"last_name\"):\n            assert attribute not in first_author[\"attributes\"]\n\n    def test_include_data_with_nested_exclude_arg(self, post):\n        data = PostSchema(\n            exclude=(\"post_comments.author.twitter\",),\n            include_data=(\"post_comments\", \"post_comments.author\"),\n        ).dump(post)\n\n        assert \"included\" in data\n        assert len(data[\"included\"]) == 4\n\n        first_author = [i for i in data[\"included\"] if i[\"type\"] == \"people\"][0]\n        assert \"twitter\" not in first_author[\"attributes\"]\n        for attribute in (\"first_name\", \"last_name\"):\n            assert attribute in first_author[\"attributes\"]\n\n    def test_include_data_load(self, post):\n        serialized = PostSchema(\n            include_data=(\"author\", \"post_comments\", \"post_comments.author\")\n        ).dump(post)\n\n        loaded = PostSchema().load(serialized)\n\n        assert \"author\" in loaded\n        assert loaded[\"author\"][\"id\"] == str(post.author.id)\n        assert loaded[\"author\"][\"first_name\"] == post.author.first_name\n\n        assert \"comments\" in loaded\n        assert len(loaded[\"comments\"]) == len(post.comments)\n        for comment in loaded[\"comments\"]:\n            assert \"body\" in comment\n            assert comment[\"id\"] in [str(c.id) for c in post.comments]\n\n    def test_include_data_load_null(self, post_with_null_author):\n        serialized = PostSchema(include_data=(\"author\", \"post_comments\")).dump(\n            post_with_null_author\n        )\n\n        with pytest.raises(ValidationError) as excinfo:\n            PostSchema().load(serialized)\n        err = excinfo.value\n        assert \"author\" in err.args[0]\n\n    def test_include_data_load_without_schema_loads_only_ids(self, post):\n        class PostInnerSchemalessSchema(Schema):\n            id = fields.Str()\n            comments = fields.Relationship(\n                \"http://test.test/posts/{id}/comments/\",\n                related_url_kwargs={\"id\": \"<id>\"},\n                data_key=\"post-comments\",\n                load_from=\"post-comments\",\n                many=True,\n                type_=\"comments\",\n            )\n\n            class Meta:\n                type_ = \"posts\"\n                strict = True\n\n        serialized = PostSchema(include_data=(\"author\", \"post_comments\")).dump(post)\n        loaded = PostInnerSchemalessSchema(unknown=INCLUDE).load(serialized)\n\n        assert \"comments\" in loaded\n        assert len(loaded[\"comments\"]) == len(post.comments)\n        for comment_id in loaded[\"comments\"]:\n            assert int(comment_id) in [c.id for c in post.comments]\n\n    def test_include_data_with_schema_context(self, post):\n        class ContextTestSchema(Schema):\n            id = fields.Str()\n            from_context = fields.Method(\"get_from_context\")\n\n            def get_from_context(self, obj):\n                return self.context[\"some_value\"]\n\n            class Meta:\n                type_ = \"people\"\n\n        class PostContextTestSchema(PostSchema):\n            author = fields.Relationship(\n                \"http://test.test/posts/{id}/author/\",\n                related_url_kwargs={\"id\": \"<id>\"},\n                schema=ContextTestSchema,\n                many=False,\n            )\n\n            class Meta(PostSchema.Meta):\n                pass\n\n        serialized = PostContextTestSchema(\n            include_data=(\"author\",), context={\"some_value\": \"Hello World\"}\n        ).dump(post)\n\n        for included in serialized[\"included\"]:\n            if included[\"type\"] == \"people\":\n                assert \"from_context\" in included[\"attributes\"]\n                assert included[\"attributes\"][\"from_context\"] == \"Hello World\"\n\n\ndef get_error_by_field(errors, field):\n    for err in errors[\"errors\"]:\n        # Relationship error pointers won't match with this.\n        if err[\"source\"][\"pointer\"].endswith(\"/\" + field):\n            return err\n    return None\n\n\nclass TestErrorFormatting:\n    def test_validate(self):\n        author = make_serialized_author({\"first_name\": \"Dan\", \"password\": \"short\"})\n        errors = AuthorSchema().validate(author)\n        assert \"errors\" in errors\n        assert len(errors[\"errors\"]) == 2\n\n        password_err = get_error_by_field(errors, \"password\")\n        assert password_err\n        assert password_err[\"detail\"] == \"Shorter than minimum length 6.\"\n\n        lname_err = get_error_by_field(errors, \"last_name\")\n        assert lname_err\n        assert lname_err[\"detail\"] == \"Missing data for required field.\"\n\n    def test_errors_in_strict_mode(self):\n        author = make_serialized_author({\"first_name\": \"Dan\", \"password\": \"short\"})\n        with pytest.raises(ValidationError) as excinfo:\n            AuthorSchema().load(author)\n        errors = excinfo.value.messages\n        assert \"errors\" in errors\n        assert len(errors[\"errors\"]) == 2\n        password_err = get_error_by_field(errors, \"password\")\n        assert password_err\n        assert password_err[\"detail\"] == \"Shorter than minimum length 6.\"\n\n        lname_err = get_error_by_field(errors, \"last_name\")\n        assert lname_err\n        assert lname_err[\"detail\"] == \"Missing data for required field.\"\n\n    def test_no_type_raises_error(self):\n        author = {\n            \"data\": {\"attributes\": {\"first_name\": \"Dan\", \"password\": \"supersecure\"}}\n        }\n        with pytest.raises(ValidationError) as excinfo:\n            AuthorSchema().load(author)\n\n        expected = {\n            \"errors\": [\n                {\n                    \"detail\": \"`data` object must include `type` key.\",\n                    \"source\": {\"pointer\": \"/data\"},\n                }\n            ]\n        }\n        assert excinfo.value.messages == expected\n\n        errors = AuthorSchema().validate(author)\n        assert errors == expected\n\n    def test_validate_no_data_raises_error(self):\n        author = {\"meta\": {\"this\": \"that\"}}\n\n        with pytest.raises(ValidationError) as excinfo:\n            AuthorSchema().load(author)\n\n        errors = excinfo.value.messages\n\n        expected = {\n            \"errors\": [\n                {\n                    \"detail\": \"Object must include `data` key.\",\n                    \"source\": {\"pointer\": \"/\"},\n                }\n            ]\n        }\n\n        assert errors == expected\n\n    def test_validate_type(self):\n        author = {\n            \"data\": {\n                \"type\": \"invalid\",\n                \"attributes\": {\"first_name\": \"Dan\", \"password\": \"supersecure\"},\n            }\n        }\n        with pytest.raises(IncorrectTypeError) as excinfo:\n            AuthorSchema().validate(author)\n        assert excinfo.value.args[0] == 'Invalid type. Expected \"people\".'\n        assert excinfo.value.messages == {\n            \"errors\": [\n                {\n                    \"detail\": 'Invalid type. Expected \"people\".',\n                    \"source\": {\"pointer\": \"/data/type\"},\n                }\n            ]\n        }\n\n    def test_validate_id(self):\n        \"\"\"the pointer for id should be at the data object, not attributes\"\"\"\n        author = {\n            \"data\": {\n                \"type\": \"people\",\n                \"id\": 123,\n                \"attributes\": {\"first_name\": \"Rob\", \"password\": \"correcthorses\"},\n            }\n        }\n        try:\n            errors = AuthorSchema().validate(author)\n        except ValidationError as err:\n            errors = err.messages\n        assert \"errors\" in errors\n        assert len(errors[\"errors\"]) == 2\n\n        lname_err = get_error_by_field(errors, \"last_name\")\n        assert lname_err\n        assert lname_err[\"source\"][\"pointer\"] == \"/data/attributes/last_name\"\n        assert lname_err[\"detail\"] == \"Missing data for required field.\"\n\n        id_err = get_error_by_field(errors, \"id\")\n        assert id_err\n        assert id_err[\"source\"][\"pointer\"] == \"/data/id\"\n        assert id_err[\"detail\"] == \"Not a valid string.\"\n\n    def test_load(self):\n        with pytest.raises(ValidationError) as excinfo:\n            AuthorSchema().load(\n                make_serialized_author({\"first_name\": \"Dan\", \"password\": \"short\"})\n            )\n        errors = excinfo.value.messages\n        assert \"errors\" in errors\n        assert len(errors[\"errors\"]) == 2\n\n        password_err = get_error_by_field(errors, \"password\")\n        assert password_err\n        assert password_err[\"detail\"] == \"Shorter than minimum length 6.\"\n\n        lname_err = get_error_by_field(errors, \"last_name\")\n        assert lname_err\n        assert lname_err[\"detail\"] == \"Missing data for required field.\"\n\n    def test_errors_is_empty_if_valid(self):\n        errors = AuthorSchema().validate(\n            make_serialized_author(\n                {\n                    \"first_name\": \"Dan\",\n                    \"last_name\": \"Gebhardt\",\n                    \"password\": \"supersecret\",\n                }\n            )\n        )\n        assert errors == {}\n\n    def test_errors_many(self):\n        authors = make_serialized_authors(\n            [\n                {\"first_name\": \"Dan\", \"last_name\": \"Gebhardt\", \"password\": \"bad\"},\n                {\n                    \"first_name\": \"Dan\",\n                    \"last_name\": \"Gebhardt\",\n                    \"password\": \"supersecret\",\n                },\n            ]\n        )\n        try:\n            errors = AuthorSchema(many=True).validate(authors)[\"errors\"]\n        except ValidationError as err:\n            errors = err.messages[\"errors\"]\n\n        assert len(errors) == 1\n\n        err = errors[0]\n        assert \"source\" in err\n        assert err[\"source\"][\"pointer\"] == \"/data/0/attributes/password\"\n\n    def test_errors_many_not_list(self):\n        authors = make_serialized_author(\n            {\"first_name\": \"Dan\", \"last_name\": \"Gebhardt\", \"password\": \"bad\"}\n        )\n        try:\n            errors = AuthorSchema(many=True).validate(authors)[\"errors\"]\n        except ValidationError as err:\n            errors = err.messages[\"errors\"]\n\n        assert len(errors) == 1\n\n        err = errors[0]\n        assert \"source\" in err\n        assert err[\"source\"][\"pointer\"] == \"/data\"\n        assert err[\"detail\"] == \"`data` expected to be a collection.\"\n\n    def test_many_id_errors(self):\n        \"\"\"the pointer for id should be at the data object, not attributes\"\"\"\n        author = {\n            \"data\": [\n                {\n                    \"type\": \"people\",\n                    \"id\": \"invalid\",\n                    \"attributes\": {\"first_name\": \"Rob\", \"password\": \"correcthorses\"},\n                },\n                {\n                    \"type\": \"people\",\n                    \"id\": 37,\n                    \"attributes\": {\n                        \"first_name\": \"Dan\",\n                        \"last_name\": \"Gebhardt\",\n                        \"password\": \"supersecret\",\n                    },\n                },\n            ]\n        }\n        errors = AuthorSchema(many=True).validate(author)\n        assert \"errors\" in errors\n        assert len(errors[\"errors\"]) == 2\n\n        lname_err = get_error_by_field(errors, \"last_name\")\n        assert lname_err\n        assert lname_err[\"source\"][\"pointer\"] == \"/data/0/attributes/last_name\"\n        assert lname_err[\"detail\"] == \"Missing data for required field.\"\n\n        id_err = get_error_by_field(errors, \"id\")\n        assert id_err\n        assert id_err[\"source\"][\"pointer\"] == \"/data/1/id\"\n        assert id_err[\"detail\"] == \"Not a valid string.\"\n\n    def test_nested_fields_error(self):\n        min_size = 10\n\n        class ThirdLevel(ma.Schema):\n            number = fields.Int(required=True, validate=ma.validate.Range(min=min_size))\n\n        class SecondLevel(ma.Schema):\n            foo = fields.Str(required=True)\n            third = fields.Nested(ThirdLevel)\n\n        class FirstLevel(Schema):\n            class Meta:\n                type_ = \"first\"\n\n            id = fields.Int()\n            second = fields.Nested(SecondLevel)\n\n        schema = FirstLevel()\n        result = schema.validate(\n            {\n                \"data\": {\n                    \"type\": \"first\",\n                    \"attributes\": {\"second\": {\"third\": {\"number\": 5}}},\n                }\n            }\n        )\n\n        def sort_func(d):\n            return d[\"source\"][\"pointer\"]\n\n        expected_errors = sorted(\n            [\n                {\n                    \"source\": {\"pointer\": \"/data/attributes/second/third/number\"},\n                    \"detail\": f\"Must be greater than or equal to {min_size}.\",\n                },\n                {\n                    \"source\": {\"pointer\": \"/data/attributes/second/foo\"},\n                    \"detail\": ma.fields.Field.default_error_messages[\"required\"],\n                },\n            ],\n            key=sort_func,\n        )\n\n        errors = sorted(result[\"errors\"], key=sort_func)\n\n        assert errors == expected_errors\n\n\nclass TestMeta:\n    shape = {\n        \"id\": 1,\n        \"sides\": 3,\n        \"meta\": \"triangle\",\n        \"resource_meta\": {\"concave\": False},\n        \"document_meta\": {\"page\": 1},\n    }\n\n    shapes = [\n        {\n            \"id\": 1,\n            \"sides\": 3,\n            \"meta\": \"triangle\",\n            \"resource_meta\": {\"concave\": False},\n            \"document_meta\": {\"page\": 1},\n        },\n        {\n            \"id\": 2,\n            \"sides\": 4,\n            \"meta\": \"quadrilateral\",\n            \"resource_meta\": {\"concave\": True},\n            \"document_meta\": {\"page\": 1},\n        },\n    ]\n\n    def test_dump_single(self):\n        serialized = PolygonSchema().dump(self.shape)\n        assert \"meta\" in serialized\n        assert serialized[\"meta\"] == self.shape[\"document_meta\"]\n        assert serialized[\"data\"][\"attributes\"][\"meta\"] == self.shape[\"meta\"]\n        assert serialized[\"data\"][\"meta\"] == self.shape[\"resource_meta\"]\n\n    def test_dump_many(self):\n        serialized = PolygonSchema(many=True).dump(self.shapes)\n        assert \"meta\" in serialized\n        assert serialized[\"meta\"] == self.shapes[0][\"document_meta\"]\n\n        first = serialized[\"data\"][0]\n        assert first[\"attributes\"][\"meta\"] == self.shapes[0][\"meta\"]\n        assert first[\"meta\"] == self.shapes[0][\"resource_meta\"]\n\n        second = serialized[\"data\"][1]\n        assert second[\"attributes\"][\"meta\"] == self.shapes[1][\"meta\"]\n        assert second[\"meta\"] == self.shapes[1][\"resource_meta\"]\n\n    def test_load_single(self):\n        serialized = PolygonSchema().dump(self.shape)\n        loaded = PolygonSchema().load(serialized)\n\n        assert loaded[\"meta\"] == self.shape[\"meta\"]\n        assert loaded[\"resource_meta\"] == self.shape[\"resource_meta\"]\n        assert loaded[\"document_meta\"] == self.shape[\"document_meta\"]\n\n    def test_load_many(self):\n        serialized = PolygonSchema(many=True).dump(self.shapes)\n        loaded = PolygonSchema(many=True).load(serialized)\n\n        first = loaded[0]\n        assert first[\"meta\"] == self.shapes[0][\"meta\"]\n        assert first[\"resource_meta\"] == self.shapes[0][\"resource_meta\"]\n        assert first[\"document_meta\"] == self.shapes[0][\"document_meta\"]\n\n        second = loaded[1]\n        assert second[\"meta\"] == self.shapes[1][\"meta\"]\n        assert second[\"resource_meta\"] == self.shapes[1][\"resource_meta\"]\n        assert second[\"document_meta\"] == self.shapes[1][\"document_meta\"]\n\n\ndef assert_relationship_error(pointer, errors):\n    \"\"\"Walk through the dictionary and determine if a specific\n    relationship pointer exists\n    \"\"\"\n    pointer = f\"/data/relationships/{pointer}/data\"\n    for error in errors:\n        if pointer == error[\"source\"][\"pointer\"]:\n            return True\n    return False\n\n\nclass TestRelationshipLoading:\n    article = {\n        \"data\": {\n            \"id\": \"1\",\n            \"type\": \"articles\",\n            \"attributes\": {\"body\": \"Test\"},\n            \"relationships\": {\n                \"author\": {\"data\": {\"type\": \"people\", \"id\": \"1\"}},\n                \"comments\": {\"data\": [{\"type\": \"comments\", \"id\": \"1\"}]},\n            },\n        }\n    }\n\n    def test_deserializing_relationship_fields(self):\n        data = ArticleSchema().load(self.article)\n        assert data[\"body\"] == \"Test\"\n        assert data[\"author\"] == \"1\"\n        assert data[\"comments\"] == [\"1\"]\n\n    def test_deserializing_nested_relationship_fields(self):\n        class RelationshipWithSchemaCommentSchema(Schema):\n            id = fields.Str()\n            body = fields.Str(required=True)\n            author = fields.Relationship(\n                schema=AuthorSchema, many=False, type_=\"people\"\n            )\n\n            class Meta:\n                type_ = \"comments\"\n                strict = True\n\n        class RelationshipWithSchemaArticleSchema(Schema):\n            id = fields.Integer()\n            body = fields.String()\n            comments = fields.Relationship(\n                schema=RelationshipWithSchemaCommentSchema, many=True, type_=\"comments\"\n            )\n            author = fields.Relationship(\n                dump_only=False,\n                include_resource_linkage=True,\n                many=False,\n                type_=\"people\",\n            )\n\n            class Meta:\n                type_ = \"articles\"\n                strict = True\n\n        article = self.article.copy()\n        article[\"included\"] = [\n            {\n                \"id\": \"1\",\n                \"type\": \"comments\",\n                \"attributes\": {\"body\": \"Test comment\"},\n                \"relationships\": {\"author\": {\"data\": {\"type\": \"people\", \"id\": \"2\"}}},\n            },\n            {\n                \"id\": \"2\",\n                \"type\": \"people\",\n                \"attributes\": {\"first_name\": \"Marshmallow Jr\", \"last_name\": \"JsonAPI\"},\n            },\n        ]\n\n        included_author = filter(\n            lambda item: item[\"type\"] == \"people\", article[\"included\"]\n        )\n        included_author = list(included_author)[0]\n\n        data = RelationshipWithSchemaArticleSchema().load(article)\n        author = data[\"comments\"][0][\"author\"]\n\n        assert isinstance(author, dict)\n        assert author[\"first_name\"] == included_author[\"attributes\"][\"first_name\"]\n\n    def test_deserializing_relationship_errors(self):\n        data = self.article\n        data[\"data\"][\"relationships\"][\"author\"][\"data\"] = {}\n        data[\"data\"][\"relationships\"][\"comments\"][\"data\"] = [{}]\n        with pytest.raises(ValidationError) as excinfo:\n            ArticleSchema().load(data)\n        errors = excinfo.value.messages\n\n        assert assert_relationship_error(\"author\", errors[\"errors\"])\n        assert assert_relationship_error(\"comments\", errors[\"errors\"])\n\n    def test_deserializing_missing_required_relationship(self):\n        class ArticleSchemaRequiredRelationships(Schema):\n            id = fields.Integer()\n            body = fields.String()\n            author = fields.Relationship(\n                dump_only=False,\n                include_resource_linkage=True,\n                many=False,\n                type_=\"people\",\n                required=True,\n            )\n            comments = fields.Relationship(\n                dump_only=False,\n                include_resource_linkage=True,\n                many=True,\n                type_=\"comments\",\n                required=True,\n            )\n\n            class Meta:\n                type_ = \"articles\"\n                strict = True\n\n        article = self.article.copy()\n        article[\"data\"][\"relationships\"] = {}\n\n        with pytest.raises(ValidationError) as excinfo:\n            ArticleSchemaRequiredRelationships().load(article)\n        errors = excinfo.value.messages\n\n        assert assert_relationship_error(\"author\", errors[\"errors\"])\n        assert assert_relationship_error(\"comments\", errors[\"errors\"])\n\n    def test_deserializing_relationship_with_missing_param(self):\n        class ArticleMissingParamSchema(Schema):\n            id = fields.Integer()\n            body = fields.String()\n            author = fields.Relationship(\n                dump_only=False,\n                include_resource_linkage=True,\n                many=False,\n                type_=\"people\",\n                missing=\"1\",\n            )\n            comments = fields.Relationship(\n                dump_only=False,\n                include_resource_linkage=True,\n                many=True,\n                type_=\"comments\",\n                missing=[\"2\", \"3\"],\n            )\n\n            class Meta:\n                type_ = \"articles\"\n                strict = True\n\n        article = self.article.copy()\n        article[\"data\"][\"relationships\"] = {}\n\n        data = ArticleMissingParamSchema().load(article)\n\n        assert \"author\" in data\n        assert data[\"author\"] == \"1\"\n        assert \"comments\" in data\n        assert data[\"comments\"] == [\"2\", \"3\"]\n"
  },
  {
    "path": "tests/test_utils.py",
    "content": "import pytest\n\nfrom marshmallow_jsonapi import utils\n\n\n@pytest.mark.parametrize(\n    \"tag,val\",\n    [\n        (\"<id>\", \"id\"),\n        (\"<author.last_name>\", \"author.last_name\"),\n        (\"<comment.author.first_name>\", \"comment.author.first_name\"),\n        (\"True\", None),\n        (\"\", None),\n    ],\n)\ndef test_tpl(tag, val):\n    assert utils.tpl(tag) == val\n"
  },
  {
    "path": "tox.ini",
    "content": "[tox]\nenvlist=\n    lint\n    py{36,37,38,39}-marshmallow3\n    py39-marshmallowdev\n    docs\n\n[testenv]\nextras = tests\ndeps =\n    marshmallow3: marshmallow>=3.0.0<4.0.0\n    marshmallowdev: https://github.com/marshmallow-code/marshmallow/archive/dev.tar.gz\ncommands = pytest {posargs}\n\n[testenv:lint]\ndeps = pre-commit~=2.0\nskip_install = true\ncommands = pre-commit run --all-files\n\n[testenv:docs]\ndeps = -rdocs/requirements.txt\nextras =\ncommands = sphinx-build docs/ docs/_build {posargs}\n\n\n; Below tasks are for development only (not run in CI)\n\n[testenv:watch-docs]\ndeps =\n  -rdocs/requirements.txt\n  sphinx-autobuild\nextras =\ncommands = sphinx-autobuild --open-browser docs/ docs/_build {posargs} --watch marshmallow_jsonapi --delay 2\n\n[testenv:watch-readme]\ndeps = restview\nskip_install = true\ncommands = restview README.rst\n"
  }
]