pax_global_header00006660000000000000000000000064145602177230014521gustar00rootroot0000000000000052 comment=f6ed1da38d829af6b6ea0d21503a1f8d0cbc151b python-flask-marshmallow-1.2.0/000077500000000000000000000000001456021772300164645ustar00rootroot00000000000000python-flask-marshmallow-1.2.0/.github/000077500000000000000000000000001456021772300200245ustar00rootroot00000000000000python-flask-marshmallow-1.2.0/.github/dependabot.yml000066400000000000000000000003301456021772300226500ustar00rootroot00000000000000version: 2 updates: - package-ecosystem: pip directory: "/" schedule: interval: daily open-pull-requests-limit: 10 - package-ecosystem: "github-actions" directory: "/" schedule: interval: "monthly" python-flask-marshmallow-1.2.0/.github/workflows/000077500000000000000000000000001456021772300220615ustar00rootroot00000000000000python-flask-marshmallow-1.2.0/.github/workflows/build-release.yml000066400000000000000000000044611456021772300253260ustar00rootroot00000000000000name: build on: push: branches: ["dev", "*.x-line"] tags: ["*"] pull_request: jobs: tests: name: ${{ matrix.name }} runs-on: ubuntu-latest strategy: fail-fast: false matrix: include: - { name: "3.8", python: "3.8", tox: py38 } - { name: "3.12", python: "3.12", tox: py312 } - { name: "lowest", python: "3.8", tox: py38-lowest } - { name: "dev", python: "3.12", tox: py312-marshmallowdev } steps: - uses: actions/checkout@v4.0.0 - uses: actions/setup-python@v5 with: python-version: ${{ matrix.python }} - run: python -m pip install tox - run: python -m tox -e ${{ matrix.tox }} build: name: Build package runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: "3.11" - name: Install pypa/build run: python -m pip install build - name: Build a binary wheel and a source tarball run: python -m build - name: Install twine run: python -m pip install twine - name: Check build run: python -m twine check --strict dist/* - name: Store the distribution packages uses: actions/upload-artifact@v4 with: name: python-package-distributions path: dist/ # this duplicates pre-commit.ci, so only run it on tags # it guarantees that linting is passing prior to a release lint-pre-release: name: lint runs-on: ubuntu-latest if: startsWith(github.ref, 'refs/tags') steps: - uses: actions/checkout@v4.0.0 - uses: actions/setup-python@v5 with: python-version: "3.11" - run: python -m pip install tox - run: python -m tox -elint publish-to-pypi: name: PyPI release if: startsWith(github.ref, 'refs/tags/') needs: [build, tests, lint-pre-release] runs-on: ubuntu-latest environment: name: pypi url: https://pypi.org/p/flask-marshmallow permissions: id-token: write steps: - name: Download all the dists uses: actions/download-artifact@v4 with: name: python-package-distributions path: dist/ - name: Publish distribution to PyPI uses: pypa/gh-action-pypi-publish@release/v1 python-flask-marshmallow-1.2.0/.gitignore000066400000000000000000000012511456021772300204530ustar00rootroot00000000000000*.py[cod] # C extensions *.so # Packages *.egg *.egg-info dist build eggs parts bin var sdist develop-eggs .installed.cfg lib lib64 pip-wheel-metadata # Installer logs pip-log.txt # Unit test / coverage reports .coverage htmlcov .tox nosetests.xml .cache .pytest_cache # Translations *.mo # Mr Developer .mr.developer.cfg # IDE .project .pydevproject .idea # Coverage cover .coveragerc # Sphinx docs/_build README.html *.ipynb .ipynb_checkpoints Vagrantfile .vagrant *.db *.ai .konchrc _sandbox pylintrc # Virtualenvs env venv # pyenv .python-version # pytest .pytest_cache # Other .directory *.pprof # mypy .mypy_cache/ .dmypy.json dmypy.json # ruff .ruff_cache python-flask-marshmallow-1.2.0/.pre-commit-config.yaml000066400000000000000000000010511456021772300227420ustar00rootroot00000000000000repos: - repo: https://github.com/astral-sh/ruff-pre-commit rev: v0.1.14 hooks: - id: ruff - id: ruff-format - repo: https://github.com/python-jsonschema/check-jsonschema rev: 0.27.3 hooks: - id: check-github-workflows - id: check-readthedocs - repo: https://github.com/asottile/blacken-docs rev: 1.16.0 hooks: - id: blacken-docs additional_dependencies: [black==23.12.1] - repo: https://github.com/pre-commit/mirrors-mypy rev: v1.8.0 hooks: - id: mypy additional_dependencies: ["marshmallow>=3,<4", "Flask"] python-flask-marshmallow-1.2.0/.readthedocs.yml000066400000000000000000000003241456021772300215510ustar00rootroot00000000000000version: 2 sphinx: configuration: docs/conf.py formats: - pdf build: os: ubuntu-22.04 tools: python: "3.11" python: install: - method: pip path: . extra_requirements: - docs python-flask-marshmallow-1.2.0/CHANGELOG.rst000066400000000000000000000210151456021772300205040ustar00rootroot00000000000000Changelog ========= 1.2.0 (2024-02-05) ****************** Features: * Performance improvement to `validate.FileSize` (:pr:`293`). Thanks :user:`uncle-lv`. 1.1.0 (2024-01-16) ****************** Features: * Add type coverage (:pr:`290`). 1.0.0 (2024-01-16) ****************** Features: * Add field ``fields.File``, ``validate.FileType``, and ``validate.FileSize`` for deserializing uploaded files (:issue:`280`, :pr:`282`). Thanks :user:`uncle-lv` for the PR * Add field ``Config`` for serializing Flask configuration values (:issue:`280`, :pr:`281`). Thanks :user:`greyli` for the PR. Support: * Support marshmallow-sqlalchemy>=0.29.0. * Test against Python 3.12. * Drop support for Python 3.7. Other changes: * *Backwards-incompatible*: Remove ```flask_marshmallow.__version__`` and ``flask_marshmallow.__version_info__`` attributes (:pr:`284`). Use feature detection or ``importlib.metadata.version("flask-marshmallow")`` instead. 0.15.0 (2023-04-05) ******************* * Changes to supported software versions. * python3.6 or later and marshmallow>=3.0.0 are now required * Add support for python3.11 * For ``sqlalchemy`` integration, marshmallow-sqlalchemy>=0.28.2 and flask-sqlalchemy>=3.0.0 are now required * *Backwards-incompatible*: ``URLFor`` and ``AbsoluteURLFor`` now do not accept parameters for ``flask.url_for`` as top-level parameters. They must always be passed in the ``values`` dictionary, as explained in the v0.14.0 changelog. Bug fixes: * Address distutils deprecation warning in Python 3.10 (:pr:`242`). Thanks :user:`GabrielLins64` for the PR. 0.14.0 (2020-09-27) ******************* * Add ``values`` argument to ``URLFor`` and ``AbsoluteURLFor`` for passing values to ``flask.url_for``. This prevents unrelated parameters from getting passed (:issue:`52`, :issue:`67`). Thanks :user:`AlrasheedA` for the PR. Deprecation: * Passing params to ``flask.url_for`` via ``URLFor``'s and ``AbsoluteURLFor``'s constructor params is deprecated. Pass ``values`` instead. .. code-block:: python # flask-marshmallow<0.14.0 class UserSchema(ma.Schema): _links = ma.Hyperlinks( { "self": ma.URLFor("user_detail", id=""), } ) # flask-marshmallow>=0.14.0 class UserSchema(ma.Schema): _links = ma.Hyperlinks( { "self": ma.URLFor("user_detail", values=dict(id="")), } ) 0.13.0 (2020-06-07) ******************* Bug fixes: * Fix compatibility with marshmallow-sqlalchemy<0.22.0 (:issue:`189`). Thanks :user:`PatrickRic` for reporting. Other changes: * Remove unused ``flask_marshmallow.sqla.SchemaOpts``. 0.12.0 (2020-04-26) ******************* * *Breaking change*: ``ma.ModelSchema`` and ``ma.TableSchema`` are removed, since these are deprecated upstream. .. warning:: It is highly recommended that you use the newer ``ma.SQLAlchemySchema`` and ``ma.SQLAlchemyAutoSchema`` classes instead of ``ModelSchema`` and ``TableSchema``. See the release notes for `marshmallow-sqlalchemy 0.22.0 `_ for instructions on how to migrate. If you need to use ``ModelSchema`` and ``TableSchema`` for the time being, you'll need to import these directly from ``marshmallow_sqlalchemy``. .. code-block:: python from flask import Flask from flask_sqlalchemy import SQLAlchemy from flask_marshmallow import Marshmallow app = Flask(__name__) app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:////tmp/test.db" db = SQLAlchemy(app) ma = Marshmallow(app) # flask-marshmallow<0.12.0 class AuthorSchema(ma.ModelSchema): class Meta: model = Author # flask-marshmallow>=0.12.0 (recommended) class AuthorSchema(ma.SQLAlchemyAutoSchema): class Meta: model = Author load_instance = True # flask-marshmallow>=0.12.0 (not recommended) from marshmallow_sqlalchemy import ModelSchema class AuthorSchema(ModelSchema): class Meta: model = Author sql_session = db.session Bug fixes: * Fix binding Flask-SQLAlchemy's scoped session to ``ma.SQLAlchemySchema`` and ``ma.SQLAlchemyAutoSchema``. (:issue:`180`). Thanks :user:`fnalonso` for reporting. 0.11.0 (2020-02-09) ******************* Features: * Add support for ``SQLAlchemySchema``, ``SQLAlchemyAutoSchema``, and ``auto_field`` from marshmallow-sqlalchemy>=0.22.0 (:pr:`166`). Bug fixes: * Properly restrict marshmallow-sqlalchemy version based on Python version (:pr:`158`). Other changes: * Test against Python 3.8. 0.10.1 (2019-05-05) ******************* Bug fixes: * marshmallow 3.0.0rc6 compatibility (:pr:`134`). 0.10.0 (2019-03-09) ******************* Features: * Add `ma.TableSchema` (:pr:`124`). * SQLAlchemy requirements can be installed with ``pip install 'flask-marshmallow[sqlalchemy]'``. Bug fixes: * ``URLFor``, ``AbsoluteURLFor``, and ``HyperlinkRelated`` serialize to ``None`` if a passed attribute value is ``None`` (:issue:`18`, :issue:`68`, :pr:`72`). Thanks :user:`RobinRamuel`, :user:`ocervell`, and :user:`feigner` for reporting. Support: * Test against Python 3.7. * Drop support for Python 3.4. Only Python 2.7 and >=3.5 are supported. 0.9.0 (2018-04-29) ****************** * Add support for marshmallow 3 beta. Thanks :user:`SBillion` for the PR. * Drop support for Python 3.3. Only Python 2.7 and >=3.4 are supported. * Updated documentation to fix example ``ma.URLFor`` target. 0.8.0 (2017-05-28) ****************** * Fix compatibility with marshmallow>=3.0. Support: * *Backwards-incompatible*: Drop support for marshmallow<=2.0.0. * Test against Python 3.6. 0.7.0 (2016-06-28) ****************** * ``many`` argument to ``Schema.jsonify`` defaults to value of the ``Schema`` instance's ``many`` attribute (:issue:`42`). Thanks :user:`singingwolfboy`. * Attach `HyperlinkRelated` to `Marshmallow` instances. Thanks :user:`singingwolfboy` for reporting. Support: * Upgrade to invoke>=0.13.0. * Updated documentation to reference `HyperlinkRelated` instead of `HyperlinkModelSchema`. Thanks :user:`singingwolfboy`. * Updated documentation links to readthedocs.io subdomain. Thanks :user:`adamchainz`. 0.6.2 (2015-09-16) ****************** * Fix compatibility with marshmallow>=2.0.0rc2. Support: * Tested against Python 3.5. 0.6.1 (2015-09-06) ****************** * Fix compatibility with marshmallow-sqlalchemy>=0.4.0 (:issue:`25`). Thanks :user:`svenstaro` for reporting. Support: * Include docs in release tarballs. 0.6.0 (2015-05-02) ****************** Features: - Add Flask-SQLAlchemy/marshmallow-sqlalchemy support via the ``ModelSchema`` and ``HyperlinkModelSchema`` classes. - ``Schema.jsonify`` now takes the same arguments as ``marshmallow.Schema.dump``. Additional keyword arguments are passed to ``flask.jsonify``. - ``Hyperlinks`` field supports serializing a list of hyperlinks (:issue:`11`). Thanks :user:`royrusso` for the suggestion. Deprecation/Removal: - Remove support for ``MARSHMALLOW_DATEFORMAT`` and ``MARSHMALLOW_STRICT`` config options. Other changes: - Drop support for marshmallow<1.2.0. 0.5.1 (2015-04-27) ****************** * Fix compatibility with marshmallow>=2.0.0. 0.5.0 (2015-03-29) ****************** * *Backwards-incompatible*: Remove ``flask_marshmallow.SchemaOpts`` class and remove support for ``MARSHMALLOW_DATEFORMAT`` and ``MARSHMALLOW_STRICT`` (:issue:`8`). Prevents a ``RuntimeError`` when instantiating a ``Schema`` outside of a request context. 0.4.0 (2014-12-22) ****************** * *Backwards-incompatible*: Rename ``URL`` and ``AbsoluteURL`` to ``URLFor`` and ``AbsoluteURLFor``, respectively, to prevent overriding marshmallow's ``URL`` field (:issue:`6`). Thanks :user:`svenstaro` for the suggestion. * Fix bug that raised an error when deserializing ``Hyperlinks`` and ``URL`` fields (:issue:`9`). Thanks :user:`raj-kesavan` for reporting. Deprecation: * ``Schema.jsonify`` is deprecated. Use ``flask.jsonify`` on the result of ``Schema.dump`` instead. * The ``MARSHMALLOW_DATEFORMAT`` and ``MARSHMALLOW_STRICT`` config values are deprecated. Use a base ``Schema`` class instead (:issue:`8`). 0.3.0 (2014-10-19) ****************** * Supports marshmallow >= 1.0.0-a. 0.2.0 (2014-05-12) ****************** * Implementation as a proper class-based Flask extension. * Serializer and fields classes are available from the ``Marshmallow`` object. 0.1.0 (2014-04-25) ****************** * First release. * ``Hyperlinks``, ``URL``, and ``AbsoluteURL`` fields implemented. * ``Serializer#jsonify`` implemented. python-flask-marshmallow-1.2.0/CONTRIBUTING.rst000066400000000000000000000100231456021772300211210ustar00rootroot00000000000000Contributing Guidelines ======================= Questions, Feature Requests, Bug Reports, and Feedback… ------------------------------------------------------- …should all be reported on the `GitHub Issue Tracker`_ . .. _`GitHub Issue Tracker`: https://github.com/marshmallow-code/flask-marshmallow/issues?state=open Contributing Code ----------------- In General ++++++++++ - `PEP 8`_, when sensible. - Test ruthlessly. Write docs for new features. - Even more important than Test-Driven Development--*Human-Driven Development*. .. _`PEP 8`: http://www.python.org/dev/peps/pep-0008/ In Particular +++++++++++++ Setting Up for Local Development ******************************** 1. Fork flask-marshmallow_ on GitHub. :: $ git clone https://github.com/marshmallow-code/flask-marshmallow.git $ cd flask-marshmallow 2. Install development requirements. **It is highly recommended that you use a virtualenv.** Use the following command to install an editable version of flask-marshmallow along with its development requirements. :: # After activating your virtualenv $ pip install -e '.[dev]' 3. (Optional, but recommended) Install the pre-commit hooks, which will format and lint your git staged files. :: # The pre-commit CLI was installed above $ pre-commit install .. note:: flask-marshmallow uses `black `_ for code formatting, which is only compatible with Python>=3.6. Therefore, the ``pre-commit install`` command will only work if you have the ``python3.6`` interpreter installed. Git Branch Structure ******************** flask-marshmallow abides by the following branching model: ``dev`` Current development branch. **New features should branch off here**. ``X.Y-line`` Maintenance branch for release ``X.Y``. **Bug fixes should be sent to the most recent release branch.**. The maintainer will forward-port the fix to ``dev``. Note: exceptions may be made for bug fixes that introduce large code changes. **Always make a new branch for your work**, no matter how small. Also, **do not put unrelated changes in the same branch or pull request**. This makes it more difficult to merge your changes. Pull Requests ************** 1. Create a new local branch. :: # For a new feature $ git checkout -b name-of-feature dev # For a bugfix $ git checkout -b fix-something 1.2-line 2. Commit your changes. Write `good commit messages `_. :: $ git commit -m "Detailed commit message" $ git push origin name-of-feature 3. Before submitting a pull request, check the following: - If the pull request adds functionality, it is tested and the docs are updated. - You've added yourself to ``AUTHORS.rst``. 4. Submit a pull request to ``marshmallow-code:dev`` or the appropriate maintenance branch. The `Travis CI `_ build must be passing before your pull request is merged. Running Tests ************* To run all tests: :: $ pytest To run syntax checks: :: $ tox -e lint (Optional) To run tests in all supported Python versions in their own virtual environments (must have each interpreter installed): :: $ tox Documentation ************* Contributions to the documentation are welcome. Documentation is written in `reStructuredText`_ (rST). A quick rST reference can be found `here `_. Builds are powered by Sphinx_. To build the docs in "watch" mode: :: $ tox -e watch-docs Changes in the `docs/` directory will automatically trigger a rebuild. Contributing Examples ********************* Have a usage example you'd like to share? Feel free to add it to the `examples `_ directory and send a pull request. .. _Sphinx: http://sphinx.pocoo.org/ .. _`reStructuredText`: https://docutils.sourceforge.io/rst.html .. _flask-marshmallow: https://github.com/marshmallow-code/flask-marshmallow python-flask-marshmallow-1.2.0/LICENSE000066400000000000000000000020501456021772300174660ustar00rootroot00000000000000Copyright Steven Loria and contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. python-flask-marshmallow-1.2.0/README.rst000066400000000000000000000067431456021772300201650ustar00rootroot00000000000000***************** Flask-Marshmallow ***************** |pypi-package| |build-status| |docs| |marshmallow3| Flask + marshmallow for beautiful APIs ====================================== Flask-Marshmallow is a thin integration layer for `Flask`_ (a Python web framework) and `marshmallow`_ (an object serialization/deserialization library) that adds additional features to marshmallow, including URL and Hyperlinks fields for HATEOAS-ready APIs. It also (optionally) integrates with `Flask-SQLAlchemy `_. Get it now ---------- :: pip install flask-marshmallow Create your app. .. code-block:: python from flask import Flask from flask_marshmallow import Marshmallow app = Flask(__name__) ma = Marshmallow(app) Write your models. .. code-block:: python from your_orm import Model, Column, Integer, String, DateTime class User(Model): email = Column(String) password = Column(String) date_created = Column(DateTime, auto_now_add=True) Define your output format with marshmallow. .. code-block:: python class UserSchema(ma.Schema): class Meta: # Fields to expose fields = ("email", "date_created", "_links") # Smart hyperlinking _links = ma.Hyperlinks( { "self": ma.URLFor("user_detail", values=dict(id="")), "collection": ma.URLFor("users"), } ) user_schema = UserSchema() users_schema = UserSchema(many=True) Output the data in your views. .. code-block:: python @app.route("/api/users/") def users(): all_users = User.all() return users_schema.dump(all_users) @app.route("/api/users/") def user_detail(id): user = User.get(id) return user_schema.dump(user) # { # "email": "fred@queen.com", # "date_created": "Fri, 25 Apr 2014 06:02:56 -0000", # "_links": { # "self": "/api/users/42", # "collection": "/api/users/" # } # } http://flask-marshmallow.readthedocs.io/ ======================================== Learn More ========== To learn more about marshmallow, check out its `docs `_. Project Links ============= - Docs: https://flask-marshmallow.readthedocs.io/ - Changelog: http://flask-marshmallow.readthedocs.io/en/latest/changelog.html - PyPI: https://pypi.org/project/flask-marshmallow/ - Issues: https://github.com/marshmallow-code/flask-marshmallow/issues License ======= MIT licensed. See the bundled `LICENSE `_ file for more details. .. _Flask: http://flask.pocoo.org .. _marshmallow: http://marshmallow.readthedocs.io .. |pypi-package| image:: https://badgen.net/pypi/v/flask-marshmallow :target: https://pypi.org/project/flask-marshmallow/ :alt: Latest version .. |build-status| image:: https://github.com/marshmallow-code/flask-marshmallow/actions/workflows/build-release.yml/badge.svg :target: https://github.com/marshmallow-code/flask-marshmallow/actions/workflows/build-release.yml :alt: Build status .. |docs| image:: https://readthedocs.org/projects/flask-marshmallow/badge/ :target: https://flask-marshmallow.readthedocs.io/ :alt: Documentation .. |marshmallow3| image:: https://badgen.net/badge/marshmallow/3 :target: https://marshmallow.readthedocs.io/en/latest/upgrading.html :alt: marshmallow 3 compatible python-flask-marshmallow-1.2.0/RELEASING.md000066400000000000000000000004231456021772300203160ustar00rootroot00000000000000# Releasing 1. Bump version in `pyproject.toml` and update the changelog with today's date. 2. Commit: `git commit -m "Bump version and update changelog"` 3. Tag the commit: `git tag x.y.z` 4. Push: `git push --tags origin dev`. CI will take care of the PyPI release. python-flask-marshmallow-1.2.0/docs/000077500000000000000000000000001456021772300174145ustar00rootroot00000000000000python-flask-marshmallow-1.2.0/docs/Makefile000066400000000000000000000151711456021772300210610ustar00rootroot00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build # User-friendly check for sphinx-build ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) endif # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" @echo " text to make text files" @echo " man to make manual pages" @echo " texinfo to make Texinfo files" @echo " info to make Texinfo files and run them through makeinfo" @echo " gettext to make PO message catalogs" @echo " changes to make an overview of all changed/added/deprecated items" @echo " xml to make Docutils-native XML files" @echo " pseudoxml to make pseudoxml-XML files for display purposes" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/complexity.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/complexity.qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/complexity" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/complexity" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." $(MAKE) -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." latexpdfja: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through platex and dvipdfmx..." $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." texinfo: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." @echo "Run \`make' in that directory to run these through makeinfo" \ "(use \`make info' here to do that automatically)." info: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo "Running Texinfo files through makeinfo..." make -C $(BUILDDIR)/texinfo info @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale @echo @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." xml: $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml @echo @echo "Build finished. The XML files are in $(BUILDDIR)/xml." pseudoxml: $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml @echo @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."python-flask-marshmallow-1.2.0/docs/_static/000077500000000000000000000000001456021772300210425ustar00rootroot00000000000000python-flask-marshmallow-1.2.0/docs/_static/logo.png000066400000000000000000000150711456021772300225140ustar00rootroot00000000000000PNG  IHDRXX)tEXtSoftwareAdobe ImageReadyqe<IDATx읿r-6C?E֩b;&Txt#1[9 d96Ͳ^`)?.ZjH0 ` ߯jLK 0 ChnnnDJZ-eZݶ{:dN=Ӥb1}V_Xp] k!RbMb߽.^S0 $-KEHO3N&LB8iÌ Q$B}{_@pq%4,\JSSQ|)~x3XR%ἑ -ʈ#W,/Xp]ÄPYsދd0}d5@b>HtAOȂ|6` _c:jDlPdr +0(Լ+6O`v{E2 @8"GX*n` \N{SllwÄi{ 3d ?}!l.rHkw,][ o]"Ħz5<^4TנH;hC'HL5|M<.WiI{E4yx/WM Bs3<lV~Ild(!媔+U[}E/E??hb.{41܍DP`hCYh&3 L'< ^㥼/t^E$VsV~6<?E)cA;y +krqUIra <e\x](jW%?V>b^*IR.w:ߟ"0^kZa#yK<~K*K[JSyir)AF䍂YzkE{uÍ]U _Wi XEU9C%f=RKa l28'D@mE'+.p[s!$vCi;q ؒ%X0%%EdP㕳G͡':ߣiP;Fq8/?Sv )@Ħ~4Dof^W]X䱕X|m=IdRUx녅i τ}~ώgرT}u}.^E =Ws =m"XVlZOt{TnVC s/Wτߞ |i8gQ$:lHOc+N뿠z4Scp_]cX ]}5B;zY}D:xH4U KK(̣i?7+0hgTM׀;%jpϖ<\d؅ {2z8F89kkZO]}eNruߘs[ 2JU ,._N Â=Z˒V ͓n,Qmzv;\6)IF ߿ it9AHEsC[5 yLq%V-? Дt懜gx$(i=Rs& ATkb/xVO{WoP"mİ0ˀg9_h!봵Z1b{ J\x"2W% &3kJx.#yl)aIl^ю%^:RetR=߰1}yjyW>N~ckB3,-+6 8[Z7:][Y!-m+[4, <+új4q%' خ%\(m ]D@@Q Õ̃2ѽA~ȓ;˶MOd,*aB$d)xWg5!wHlJo^v^buU;B`^}!rS_޶V@`;E'2vuP1hx&nnnyպb='|@|=4Y,8Վ,qCzyJ} ,Z`ǥ v̵sUGÝ&oZly:9ۉڟ0hx^ThRL]˃;+ŞR'caRBel%"yV&6=oX{,2"GWhy[_ՊLդsc@=LMN5LuP)zҶ=G0&ߎ}Vt /fʬ@*$/\+ ZS4+aN;wZie.>Cd&mH^彆Mt#=6qf쩳& =y5tUwM#+Z7N1un0j,{70=̥^;pHKWyǖ :x st*G;$VUS>j 4ʨkCc2{>g.22XQXi =`*šP6[`m̆ļEͅ}M|#l"A5sMTnpk*|!,)quVO1S[+`>ɝͻ/Hb Ec-h2r`b@7Epzk)VX,,N.tѵ.C2[c[E玴gK4fm^"%el AIG"HO+E]S^?:tX@=Gࡻ JXaŋ^"~%6ֹfacbzK%؋ @EOH7j >lC~H^`e'گ-,%&n_~ZPIENDB`python-flask-marshmallow-1.2.0/docs/_templates/000077500000000000000000000000001456021772300215515ustar00rootroot00000000000000python-flask-marshmallow-1.2.0/docs/_templates/side-primary.html000066400000000000000000000016301456021772300250440ustar00rootroot00000000000000

Flask-Marshmallow Home

Flask + marshmallow for beautiful APIs

Useful Links

Stay Informed

python-flask-marshmallow-1.2.0/docs/_templates/side-secondary.html000066400000000000000000000013201456021772300253440ustar00rootroot00000000000000

Flask-Marshmallow Home

Flask + marshmallow for beautiful APIs

Useful Links

python-flask-marshmallow-1.2.0/docs/_themes/000077500000000000000000000000001456021772300210405ustar00rootroot00000000000000python-flask-marshmallow-1.2.0/docs/_themes/LICENSE000066400000000000000000000033751456021772300220550ustar00rootroot00000000000000Copyright (c) 2010 by Armin Ronacher. Some rights reserved. Redistribution and use in source and binary forms of the theme, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * The names of the contributors may not be used to endorse or promote products derived from this software without specific prior written permission. We kindly ask you to only use these themes in an unmodified manner just for Flask and Flask-related products, not for unrelated projects. If you like the visual style and want to use it for your own projects, please consider making some larger changes to the themes (such as changing font faces, sizes, colors or margins). THIS THEME IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS THEME, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. python-flask-marshmallow-1.2.0/docs/_themes/README000066400000000000000000000021051456021772300217160ustar00rootroot00000000000000Flask Sphinx Styles =================== This repository contains sphinx styles for Flask and Flask related projects. To use this style in your Sphinx documentation, follow this guide: 1. put this folder as _themes into your docs folder. Alternatively you can also use git submodules to check out the contents there. 2. add this to your conf.py: sys.path.append(os.path.abspath('_themes')) html_theme_path = ['_themes'] html_theme = 'flask' The following themes exist: - 'flask' - the standard flask documentation theme for large projects - 'flask_small' - small one-page theme. Intended to be used by very small addon libraries for flask. The following options exist for the flask_small theme: [options] index_logo = '' filename of a picture in _static to be used as replacement for the h1 in the index.rst file. index_logo_height = 120px height of the index logo github_fork = '' repository name on github for the "fork me" badge python-flask-marshmallow-1.2.0/docs/_themes/flask/000077500000000000000000000000001456021772300221405ustar00rootroot00000000000000python-flask-marshmallow-1.2.0/docs/_themes/flask/layout.html000066400000000000000000000012651456021772300243470ustar00rootroot00000000000000{%- extends "basic/layout.html" %} {%- block extrahead %} {{ super() }} {% if theme_touch_icon %} {% endif %} {% endblock %} {%- block relbar2 %}{% endblock %} {% block header %} {{ super() }} {% if pagename == 'index' %}
{% endif %} {% endblock %} {%- block footer %} {% if pagename == 'index' %}
{% endif %} {%- endblock %} python-flask-marshmallow-1.2.0/docs/_themes/flask/relations.html000066400000000000000000000011161456021772300250250ustar00rootroot00000000000000

Related Topics

python-flask-marshmallow-1.2.0/docs/_themes/flask/static/000077500000000000000000000000001456021772300234275ustar00rootroot00000000000000python-flask-marshmallow-1.2.0/docs/_themes/flask/static/flasky.css_t000066400000000000000000000215461456021772300257650ustar00rootroot00000000000000/* * flasky.css_t * ~~~~~~~~~~~~ * * :copyright: Copyright 2010 by Armin Ronacher. * :license: Flask Design License, see LICENSE for details. */ {% set page_width = '940px' %} {% set sidebar_width = '220px' %} @import url("basic.css"); /* -- page layout ----------------------------------------------------------- */ body { font-family: 'Georgia', serif; font-size: 17px; background-color: white; color: #000; margin: 0; padding: 0; } div.document { width: {{ page_width }}; margin: 30px auto 0 auto; } div.documentwrapper { float: left; width: 100%; } div.bodywrapper { margin: 0 0 0 {{ sidebar_width }}; } div.sphinxsidebar { width: {{ sidebar_width }}; } hr { border: 1px solid #B1B4B6; } div.body { background-color: #ffffff; color: #3E4349; padding: 0 30px 0 30px; } img.floatingflask { padding: 0 0 10px 10px; float: right; } div.footer { width: {{ page_width }}; margin: 20px auto 30px auto; font-size: 14px; color: #888; text-align: right; } div.footer a { color: #888; } div.related { display: none; } div.sphinxsidebar a { color: #444; text-decoration: none; border-bottom: 1px dotted #999; } div.sphinxsidebar a:hover { border-bottom: 1px solid #999; } div.sphinxsidebar { font-size: 14px; line-height: 1.5; } div.sphinxsidebarwrapper { padding: 18px 10px; } div.sphinxsidebarwrapper p.logo { padding: 0 0 20px 0; margin: 0; text-align: center; } div.sphinxsidebar h3, div.sphinxsidebar h4 { font-family: 'Garamond', 'Georgia', serif; color: #444; font-size: 24px; font-weight: normal; margin: 0 0 5px 0; padding: 0; } div.sphinxsidebar h4 { font-size: 20px; } div.sphinxsidebar h3 a { color: #444; } div.sphinxsidebar p.logo a, div.sphinxsidebar h3 a, div.sphinxsidebar p.logo a:hover, div.sphinxsidebar h3 a:hover { border: none; } div.sphinxsidebar p { color: #555; margin: 10px 0; } div.sphinxsidebar ul { margin: 10px 0; padding: 0; color: #000; } div.sphinxsidebar input { border: 1px solid #ccc; font-family: 'Georgia', serif; font-size: 1em; } /* -- body styles ----------------------------------------------------------- */ a { color: #004B6B; text-decoration: underline; } a:hover { color: #6D4100; text-decoration: underline; } div.body h1, div.body h2, div.body h3, div.body h4, div.body h5, div.body h6 { font-family: 'Garamond', 'Georgia', serif; font-weight: normal; margin: 30px 0px 10px 0px; padding: 0; } {% if theme_index_logo %} div.indexwrapper h1 { text-indent: -999999px; background: url({{ theme_index_logo }}) no-repeat center center; height: {{ theme_index_logo_height }}; } {% endif %} div.body h1 { margin-top: 0; padding-top: 0; font-size: 240%; } div.body h2 { font-size: 180%; } div.body h3 { font-size: 150%; } div.body h4 { font-size: 130%; } div.body h5 { font-size: 100%; } div.body h6 { font-size: 100%; } a.headerlink { color: #ddd; padding: 0 4px; text-decoration: none; } a.headerlink:hover { color: #444; background: #eaeaea; } div.body p, div.body dd, div.body li { line-height: 1.4em; } div.admonition { background: #fafafa; margin: 20px -30px; padding: 10px 30px; border-top: 1px solid #ccc; border-bottom: 1px solid #ccc; } div.admonition tt.xref, div.admonition a tt { border-bottom: 1px solid #fafafa; } dd div.admonition { margin-left: -60px; padding-left: 60px; } div.admonition p.admonition-title { font-family: 'Garamond', 'Georgia', serif; font-weight: normal; font-size: 24px; margin: 0 0 10px 0; padding: 0; line-height: 1; } div.admonition p.last { margin-bottom: 0; } div.highlight { background-color: white; } dt:target, .highlight { background: #FAF3E8; } div.note { background-color: #eee; border: 1px solid #ccc; } div.seealso { background-color: #ffc; border: 1px solid #ff6; } div.topic { background-color: #eee; } p.admonition-title { display: inline; } p.admonition-title:after { content: ":"; } pre, tt { font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace; font-size: 0.9em; } img.screenshot { } tt.descname, tt.descclassname { font-size: 0.95em; } tt.descname { padding-right: 0.08em; } img.screenshot { -moz-box-shadow: 2px 2px 4px #eee; -webkit-box-shadow: 2px 2px 4px #eee; box-shadow: 2px 2px 4px #eee; } table.docutils { border: 1px solid #888; -moz-box-shadow: 2px 2px 4px #eee; -webkit-box-shadow: 2px 2px 4px #eee; box-shadow: 2px 2px 4px #eee; } table.docutils td, table.docutils th { border: 1px solid #888; padding: 0.25em 0.7em; } table.field-list, table.footnote { border: none; -moz-box-shadow: none; -webkit-box-shadow: none; box-shadow: none; } table.footnote { margin: 15px 0; width: 100%; border: 1px solid #eee; background: #fdfdfd; font-size: 0.9em; } table.footnote + table.footnote { margin-top: -15px; border-top: none; } table.field-list th { padding: 0 0.8em 0 0; } table.field-list td { padding: 0; } table.footnote td.label { width: 0px; padding: 0.3em 0 0.3em 0.5em; } table.footnote td { padding: 0.3em 0.5em; } dl { margin: 0; padding: 0; } dl dd { margin-left: 30px; } blockquote { margin: 0 0 0 30px; padding: 0; } ul, ol { margin: 10px 0 10px 30px; padding: 0; } pre { background: #eee; padding: 7px 30px; margin: 15px -30px; line-height: 1.3em; } dl pre, blockquote pre, li pre { margin-left: -60px; padding-left: 60px; } dl dl pre { margin-left: -90px; padding-left: 90px; } tt { background-color: #ecf0f3; color: #222; /* padding: 1px 2px; */ } tt.xref, a tt { background-color: #FBFBFB; border-bottom: 1px solid white; } a.reference { text-decoration: none; border-bottom: 1px dotted #004B6B; } a.reference:hover { border-bottom: 1px solid #6D4100; } a.footnote-reference { text-decoration: none; font-size: 0.7em; vertical-align: top; border-bottom: 1px dotted #004B6B; } a.footnote-reference:hover { border-bottom: 1px solid #6D4100; } a:hover tt { background: #EEE; } @media screen and (max-width: 870px) { div.sphinxsidebar { display: none; } div.document { width: 100%; } div.documentwrapper { margin-left: 0; margin-top: 0; margin-right: 0; margin-bottom: 0; } div.bodywrapper { margin-top: 0; margin-right: 0; margin-bottom: 0; margin-left: 0; } ul { margin-left: 0; } .document { width: auto; } .footer { width: auto; } .bodywrapper { margin: 0; } .footer { width: auto; } .github { display: none; } } @media screen and (max-width: 875px) { body { margin: 0; padding: 20px 30px; } div.documentwrapper { float: none; background: white; } div.sphinxsidebar { display: block; float: none; width: 102.5%; margin: 50px -30px -20px -30px; padding: 10px 20px; background: #333; color: white; } div.sphinxsidebar h3, div.sphinxsidebar h4, div.sphinxsidebar p, div.sphinxsidebar h3 a { color: white; } div.sphinxsidebar a { color: #aaa; } div.sphinxsidebar p.logo { display: none; } div.document { width: 100%; margin: 0; } div.related { display: block; margin: 0; padding: 10px 0 20px 0; } div.related ul, div.related ul li { margin: 0; padding: 0; } div.footer { display: none; } div.bodywrapper { margin: 0; } div.body { min-height: 0; padding: 0; } .rtd_doc_footer { display: none; } .document { width: auto; } .footer { width: auto; } .footer { width: auto; } .github { display: none; } } /* scrollbars */ ::-webkit-scrollbar { width: 6px; height: 6px; } ::-webkit-scrollbar-button:start:decrement, ::-webkit-scrollbar-button:end:increment { display: block; height: 10px; } ::-webkit-scrollbar-button:vertical:increment { background-color: #fff; } ::-webkit-scrollbar-track-piece { background-color: #eee; -webkit-border-radius: 3px; } ::-webkit-scrollbar-thumb:vertical { height: 50px; background-color: #ccc; -webkit-border-radius: 3px; } ::-webkit-scrollbar-thumb:horizontal { width: 50px; background-color: #ccc; -webkit-border-radius: 3px; } /* misc. */ .revsys-inline { display: none!important; }python-flask-marshmallow-1.2.0/docs/_themes/flask/theme.conf000066400000000000000000000002441456021772300241110ustar00rootroot00000000000000[theme] inherit = basic stylesheet = flasky.css pygments_style = flask_theme_support.FlaskyStyle [options] index_logo = '' index_logo_height = 120px touch_icon = python-flask-marshmallow-1.2.0/docs/_themes/flask_small/000077500000000000000000000000001456021772300233305ustar00rootroot00000000000000python-flask-marshmallow-1.2.0/docs/_themes/flask_small/layout.html000066400000000000000000000012661456021772300255400ustar00rootroot00000000000000{% extends "basic/layout.html" %} {% block header %} {{ super() }} {% if pagename == 'index' %}
{% endif %} {% endblock %} {% block footer %} {% if pagename == 'index' %}
{% endif %} {% endblock %} {# do not display relbars #} {% block relbar1 %}{% endblock %} {% block relbar2 %} {% if theme_github_fork %} Fork me on GitHub {% endif %} {% endblock %} {% block sidebar1 %}{% endblock %} {% block sidebar2 %}{% endblock %} python-flask-marshmallow-1.2.0/docs/_themes/flask_small/static/000077500000000000000000000000001456021772300246175ustar00rootroot00000000000000python-flask-marshmallow-1.2.0/docs/_themes/flask_small/static/flasky.css_t000066400000000000000000000111761456021772300271530ustar00rootroot00000000000000/* * flasky.css_t * ~~~~~~~~~~~~ * * Sphinx stylesheet -- flasky theme based on nature theme. * * :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS. * :license: BSD, see LICENSE for details. * */ @import url("basic.css"); /* -- page layout ----------------------------------------------------------- */ body { font-family: 'Georgia', serif; font-size: 17px; color: #000; background: white; margin: 0; padding: 0; } div.documentwrapper { float: left; width: 100%; } div.bodywrapper { margin: 40px auto 0 auto; width: 700px; } hr { border: 1px solid #B1B4B6; } div.body { background-color: #ffffff; color: #3E4349; padding: 0 30px 30px 30px; } img.floatingflask { padding: 0 0 10px 10px; float: right; } div.footer { text-align: right; color: #888; padding: 10px; font-size: 14px; width: 650px; margin: 0 auto 40px auto; } div.footer a { color: #888; text-decoration: underline; } div.related { line-height: 32px; color: #888; } div.related ul { padding: 0 0 0 10px; } div.related a { color: #444; } /* -- body styles ----------------------------------------------------------- */ a { color: #004B6B; text-decoration: underline; } a:hover { color: #6D4100; text-decoration: underline; } div.body { padding-bottom: 40px; /* saved for footer */ } div.body h1, div.body h2, div.body h3, div.body h4, div.body h5, div.body h6 { font-family: 'Garamond', 'Georgia', serif; font-weight: normal; margin: 30px 0px 10px 0px; padding: 0; } {% if theme_index_logo %} div.indexwrapper h1 { text-indent: -999999px; background: url({{ theme_index_logo }}) no-repeat center center; height: {{ theme_index_logo_height }}; } {% endif %} div.body h2 { font-size: 180%; } div.body h3 { font-size: 150%; } div.body h4 { font-size: 130%; } div.body h5 { font-size: 100%; } div.body h6 { font-size: 100%; } a.headerlink { color: white; padding: 0 4px; text-decoration: none; } a.headerlink:hover { color: #444; background: #eaeaea; } div.body p, div.body dd, div.body li { line-height: 1.4em; } div.admonition { background: #fafafa; margin: 20px -30px; padding: 10px 30px; border-top: 1px solid #ccc; border-bottom: 1px solid #ccc; } div.admonition p.admonition-title { font-family: 'Garamond', 'Georgia', serif; font-weight: normal; font-size: 24px; margin: 0 0 10px 0; padding: 0; line-height: 1; } div.admonition p.last { margin-bottom: 0; } div.highlight{ background-color: white; } dt:target, .highlight { background: #FAF3E8; } div.note { background-color: #eee; border: 1px solid #ccc; } div.seealso { background-color: #ffc; border: 1px solid #ff6; } div.topic { background-color: #eee; } div.warning { background-color: #ffe4e4; border: 1px solid #f66; } p.admonition-title { display: inline; } p.admonition-title:after { content: ":"; } pre, tt { font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace; font-size: 0.85em; } img.screenshot { } tt.descname, tt.descclassname { font-size: 0.95em; } tt.descname { padding-right: 0.08em; } img.screenshot { -moz-box-shadow: 2px 2px 4px #eee; -webkit-box-shadow: 2px 2px 4px #eee; box-shadow: 2px 2px 4px #eee; } table.docutils { border: 1px solid #888; -moz-box-shadow: 2px 2px 4px #eee; -webkit-box-shadow: 2px 2px 4px #eee; box-shadow: 2px 2px 4px #eee; } table.docutils td, table.docutils th { border: 1px solid #888; padding: 0.25em 0.7em; } table.field-list, table.footnote { border: none; -moz-box-shadow: none; -webkit-box-shadow: none; box-shadow: none; } table.footnote { margin: 15px 0; width: 100%; border: 1px solid #eee; } table.field-list th { padding: 0 0.8em 0 0; } table.field-list td { padding: 0; } table.footnote td { padding: 0.5em; } dl { margin: 0; padding: 0; } dl dd { margin-left: 30px; } pre { padding: 0; margin: 15px -30px; padding: 8px; line-height: 1.3em; padding: 7px 30px; background: #eee; border-radius: 2px; -moz-border-radius: 2px; -webkit-border-radius: 2px; } dl pre { margin-left: -60px; padding-left: 60px; } tt { background-color: #ecf0f3; color: #222; /* padding: 1px 2px; */ } tt.xref, a tt { background-color: #FBFBFB; } a:hover tt { background: #EEE; } /* mods by sloria */ /* hack to make header links consistently styled */ .reference.internal em { font-style: normal; } python-flask-marshmallow-1.2.0/docs/_themes/flask_small/theme.conf000066400000000000000000000002571456021772300253050ustar00rootroot00000000000000[theme] inherit = basic stylesheet = flasky.css nosidebar = true pygments_style = flask_theme_support.FlaskyStyle [options] index_logo = 'logo.png' index_logo_height = 120px python-flask-marshmallow-1.2.0/docs/_themes/flask_theme_support.py000066400000000000000000000075011456021772300254730ustar00rootroot00000000000000# flasky extensions. flasky pygments style based on tango style from pygments.style import Style from pygments.token import ( Comment, Error, Generic, Keyword, Literal, Name, Number, Operator, Other, Punctuation, String, Whitespace, ) class FlaskyStyle(Style): background_color = "#f8f8f8" default_style = "" styles = { # No corresponding class for the following: # Text: "", # class: '' Whitespace: "underline #f8f8f8", # class: 'w' Error: "#a40000 border:#ef2929", # class: 'err' Other: "#000000", # class 'x' Comment: "italic #8f5902", # class: 'c' Comment.Preproc: "noitalic", # class: 'cp' Keyword: "bold #004461", # class: 'k' Keyword.Constant: "bold #004461", # class: 'kc' Keyword.Declaration: "bold #004461", # class: 'kd' Keyword.Namespace: "bold #004461", # class: 'kn' Keyword.Pseudo: "bold #004461", # class: 'kp' Keyword.Reserved: "bold #004461", # class: 'kr' Keyword.Type: "bold #004461", # class: 'kt' Operator: "#582800", # class: 'o' Operator.Word: "bold #004461", # class: 'ow' - like keywords Punctuation: "bold #000000", # class: 'p' # because special names such as Name.Class, Name.Function, etc. # are not recognized as such later in the parsing, we choose them # to look the same as ordinary variables. Name: "#000000", # class: 'n' Name.Attribute: "#c4a000", # class: 'na' - to be revised Name.Builtin: "#004461", # class: 'nb' Name.Builtin.Pseudo: "#3465a4", # class: 'bp' Name.Class: "#000000", # class: 'nc' - to be revised Name.Constant: "#000000", # class: 'no' - to be revised Name.Decorator: "#888", # class: 'nd' - to be revised Name.Entity: "#ce5c00", # class: 'ni' Name.Exception: "bold #cc0000", # class: 'ne' Name.Function: "#000000", # class: 'nf' Name.Property: "#000000", # class: 'py' Name.Label: "#f57900", # class: 'nl' Name.Namespace: "#000000", # class: 'nn' - to be revised Name.Other: "#000000", # class: 'nx' Name.Tag: "bold #004461", # class: 'nt' - like a keyword Name.Variable: "#000000", # class: 'nv' - to be revised Name.Variable.Class: "#000000", # class: 'vc' - to be revised Name.Variable.Global: "#000000", # class: 'vg' - to be revised Name.Variable.Instance: "#000000", # class: 'vi' - to be revised Number: "#990000", # class: 'm' Literal: "#000000", # class: 'l' Literal.Date: "#000000", # class: 'ld' String: "#4e9a06", # class: 's' String.Backtick: "#4e9a06", # class: 'sb' String.Char: "#4e9a06", # class: 'sc' String.Doc: "italic #8f5902", # class: 'sd' - like a comment String.Double: "#4e9a06", # class: 's2' String.Escape: "#4e9a06", # class: 'se' String.Heredoc: "#4e9a06", # class: 'sh' String.Interpol: "#4e9a06", # class: 'si' String.Other: "#4e9a06", # class: 'sx' String.Regex: "#4e9a06", # class: 'sr' String.Single: "#4e9a06", # class: 's1' String.Symbol: "#4e9a06", # class: 'ss' Generic: "#000000", # class: 'g' Generic.Deleted: "#a40000", # class: 'gd' Generic.Emph: "italic #000000", # class: 'ge' Generic.Error: "#ef2929", # class: 'gr' Generic.Heading: "bold #000080", # class: 'gh' Generic.Inserted: "#00A000", # class: 'gi' Generic.Output: "#888", # class: 'go' Generic.Prompt: "#745334", # class: 'gp' Generic.Strong: "bold #000000", # class: 'gs' Generic.Subheading: "bold #800080", # class: 'gu' Generic.Traceback: "bold #a40000", # class: 'gt' } python-flask-marshmallow-1.2.0/docs/changelog.rst000066400000000000000000000000561456021772300220760ustar00rootroot00000000000000.. _changelog: .. include:: ../CHANGELOG.rst python-flask-marshmallow-1.2.0/docs/conf.py000077500000000000000000000030051456021772300207140ustar00rootroot00000000000000import importlib.metadata import os import sys sys.path.append(os.path.abspath("_themes")) extensions = ["sphinx.ext.autodoc", "sphinx.ext.intersphinx", "sphinx_issues"] intersphinx_mapping = { "python": ("http://python.readthedocs.io/en/latest/", None), "flask": ("http://flask.pocoo.org/docs/latest/", None), "flask-sqlalchemy": ("http://flask-sqlalchemy.pocoo.org/latest/", None), "marshmallow": ("http://marshmallow.readthedocs.io/en/latest/", None), "marshmallow-sqlalchemy": ( "http://marshmallow-sqlalchemy.readthedocs.io/en/latest/", None, ), } primary_domain = "py" default_role = "py:obj" issues_github_path = "marshmallow-code/flask-marshmallow" # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] # The suffix of source filenames. source_suffix = ".rst" # The master toctree document. master_doc = "index" # General information about the project. project = "Flask-Marshmallow" copyright = "Steven Loria and contributors" version = release = importlib.metadata.version("flask-marshmallow") exclude_patterns = ["_build"] # The name of the Pygments (syntax highlighting) style to use. pygments_style = "flask_theme_support.FlaskyStyle" html_theme = "flask_small" html_theme_path = ["_themes"] html_static_path = ["_static"] html_sidebars = { "index": ["side-primary.html", "searchbox.html"], "**": ["side-secondary.html", "localtoc.html", "relations.html", "searchbox.html"], } htmlhelp_basename = "flask-marshmallowdoc" python-flask-marshmallow-1.2.0/docs/index.rst000066400000000000000000000171271456021772300212650ustar00rootroot00000000000000********************************************************* Flask-Marshmallow: Flask + marshmallow for beautiful APIs ********************************************************* :ref:`changelog ` // `github `_ // `pypi `_ // `issues `_ Flask + marshmallow for beautiful APIs ====================================== Flask-Marshmallow is a thin integration layer for `Flask`_ (a Python web framework) and `marshmallow`_ (an object serialization/deserialization library) that adds additional features to marshmallow, including URL and Hyperlinks fields for HATEOAS-ready APIs. It also (optionally) integrates with `Flask-SQLAlchemy `_. Get it now ---------- :: pip install flask-marshmallow Create your app. .. code-block:: python from flask import Flask from flask_marshmallow import Marshmallow app = Flask(__name__) ma = Marshmallow(app) Write your models. .. code-block:: python from your_orm import Model, Column, Integer, String, DateTime class User(Model): email = Column(String) password = Column(String) date_created = Column(DateTime, auto_now_add=True) Define your output format with marshmallow. .. code-block:: python class UserSchema(ma.Schema): class Meta: # Fields to expose fields = ("email", "date_created", "_links") # Smart hyperlinking _links = ma.Hyperlinks( { "self": ma.URLFor("user_detail", values=dict(id="")), "collection": ma.URLFor("users"), } ) user_schema = UserSchema() users_schema = UserSchema(many=True) Output the data in your views. .. code-block:: python @app.route("/api/users/") def users(): all_users = User.all() return users_schema.dump(all_users) @app.route("/api/users/") def user_detail(id): user = User.get(id) return user_schema.dump(user) # { # "email": "fred@queen.com", # "date_created": "Fri, 25 Apr 2014 06:02:56 -0000", # "_links": { # "self": "/api/users/42", # "collection": "/api/users/" # } # } Optional Flask-SQLAlchemy Integration ------------------------------------- Flask-Marshmallow includes useful extras for integrating with `Flask-SQLAlchemy `_ and `marshmallow-sqlalchemy `_. To enable SQLAlchemy integration, make sure that both Flask-SQLAlchemy and marshmallow-sqlalchemy are installed. :: pip install -U flask-sqlalchemy marshmallow-sqlalchemy Next, initialize the `~flask_sqlalchemy.SQLAlchemy` and `~flask_marshmallow.Marshmallow` extensions, in that order. .. code-block:: python from flask import Flask from flask_sqlalchemy import SQLAlchemy from flask_marshmallow import Marshmallow app = Flask(__name__) app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:////tmp/test.db" # Order matters: Initialize SQLAlchemy before Marshmallow db = SQLAlchemy(app) ma = Marshmallow(app) .. admonition:: Note on initialization order Flask-SQLAlchemy **must** be initialized before Flask-Marshmallow. Declare your models like normal. .. code-block:: python class Author(db.Model): id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(255)) class Book(db.Model): id = db.Column(db.Integer, primary_key=True) title = db.Column(db.String(255)) author_id = db.Column(db.Integer, db.ForeignKey("author.id")) author = db.relationship("Author", backref="books") Generate marshmallow `Schemas ` from your models using `~flask_marshmallow.sqla.SQLAlchemySchema` or `~flask_marshmallow.sqla.SQLAlchemyAutoSchema`. .. code-block:: python class AuthorSchema(ma.SQLAlchemySchema): class Meta: model = Author id = ma.auto_field() name = ma.auto_field() books = ma.auto_field() class BookSchema(ma.SQLAlchemyAutoSchema): class Meta: model = Book include_fk = True You can now use your schema to dump and load your ORM objects. .. code-block:: python db.create_all() author_schema = AuthorSchema() book_schema = BookSchema() author = Author(name="Chuck Paluhniuk") book = Book(title="Fight Club", author=author) db.session.add(author) db.session.add(book) db.session.commit() author_schema.dump(author) # {'id': 1, 'name': 'Chuck Paluhniuk', 'books': [1]} `~flask_marshmallow.sqla.SQLAlchemySchema` is nearly identical in API to `marshmallow_sqlalchemy.SQLAlchemySchema` with the following exceptions: - By default, `~flask_marshmallow.sqla.SQLAlchemySchema` uses the scoped session created by Flask-SQLAlchemy. - `~flask_marshmallow.sqla.SQLAlchemySchema` subclasses `flask_marshmallow.Schema`, so it includes the `~flask_marshmallow.Schema.jsonify` method. Note: By default, Flask's `jsonify` method sorts the list of keys and returns consistent results to ensure that external HTTP caches aren't trashed. As a side effect, this will override `ordered=True `_ in the SQLAlchemySchema's `class Meta` (if you set it). To disable this, set `JSON_SORT_KEYS=False` in your Flask app config. In production it's recommended to let `jsonify` sort the keys and not set `ordered=True` in your `~flask_marshmallow.sqla.SQLAlchemySchema` in order to minimize generation time and maximize cacheability of the results. You can also use `ma.HyperlinkRelated ` fields if you want relationships to be represented by hyperlinks rather than primary keys. .. code-block:: python class BookSchema(ma.SQLAlchemyAutoSchema): class Meta: model = Book author = ma.HyperlinkRelated("author_detail") .. code-block:: python with app.test_request_context(): print(book_schema.dump(book)) # {'id': 1, 'title': 'Fight Club', 'author': '/authors/1'} The first argument to the `~flask_marshmallow.sqla.HyperlinkRelated` constructor is the name of the view used to generate the URL, just as you would pass it to the `~flask.url_for` function. If your models and views use the ``id`` attribute as a primary key, you're done; otherwise, you must specify the name of the attribute used as the primary key. To represent a one-to-many relationship, wrap the `~flask_marshmallow.sqla.HyperlinkRelated` instance in a `marshmallow.fields.List` field, like this: .. code-block:: python class AuthorSchema(ma.SQLAlchemyAutoSchema): class Meta: model = Author books = ma.List(ma.HyperlinkRelated("book_detail")) .. code-block:: python with app.test_request_context(): print(author_schema.dump(author)) # {'id': 1, 'name': 'Chuck Paluhniuk', 'books': ['/books/1']} API === .. automodule:: flask_marshmallow :members: .. automodule:: flask_marshmallow.fields :members: .. automodule:: flask_marshmallow.validate :members: .. automodule:: flask_marshmallow.sqla :members: Useful Links ============ - `Flask docs`_ - `marshmallow docs`_ .. _marshmallow docs: http://marshmallow.readthedocs.io .. _Flask docs: http://flask.pocoo.org/docs/ Project Info ============ .. toctree:: :maxdepth: 1 license changelog .. _marshmallow: http://marshmallow.readthedocs.io .. _Flask: http://flask.pocoo.org python-flask-marshmallow-1.2.0/docs/license.rst000066400000000000000000000000701456021772300215650ustar00rootroot00000000000000******* License ******* .. literalinclude:: ../LICENSE python-flask-marshmallow-1.2.0/docs/make.bat000066400000000000000000000145021456021772300210230ustar00rootroot00000000000000@ECHO OFF REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set BUILDDIR=_build set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . set I18NSPHINXOPTS=%SPHINXOPTS% . if NOT "%PAPER%" == "" ( set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% ) if "%1" == "" goto help if "%1" == "help" ( :help echo.Please use `make ^` where ^ is one of echo. html to make standalone HTML files echo. dirhtml to make HTML files named index.html in directories echo. singlehtml to make a single large HTML file echo. pickle to make pickle files echo. json to make JSON files echo. htmlhelp to make HTML files and a HTML help project echo. qthelp to make HTML files and a qthelp project echo. devhelp to make HTML files and a Devhelp project echo. epub to make an epub echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter echo. text to make text files echo. man to make manual pages echo. texinfo to make Texinfo files echo. gettext to make PO message catalogs echo. changes to make an overview over all changed/added/deprecated items echo. xml to make Docutils-native XML files echo. pseudoxml to make pseudoxml-XML files for display purposes echo. linkcheck to check all external links for integrity echo. doctest to run all doctests embedded in the documentation if enabled goto end ) if "%1" == "clean" ( for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i del /q /s %BUILDDIR%\* goto end ) %SPHINXBUILD% 2> nul if errorlevel 9009 ( echo. echo.The 'sphinx-build' command was not found. Make sure you have Sphinx echo.installed, then set the SPHINXBUILD environment variable to point echo.to the full path of the 'sphinx-build' executable. Alternatively you echo.may add the Sphinx directory to PATH. echo. echo.If you don't have Sphinx installed, grab it from echo.http://sphinx-doc.org/ exit /b 1 ) if "%1" == "html" ( %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/html. goto end ) if "%1" == "dirhtml" ( %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. goto end ) if "%1" == "singlehtml" ( %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. goto end ) if "%1" == "pickle" ( %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the pickle files. goto end ) if "%1" == "json" ( %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the JSON files. goto end ) if "%1" == "htmlhelp" ( %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run HTML Help Workshop with the ^ .hhp project file in %BUILDDIR%/htmlhelp. goto end ) if "%1" == "qthelp" ( %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run "qcollectiongenerator" with the ^ .qhcp project file in %BUILDDIR%/qthelp, like this: echo.^> qcollectiongenerator %BUILDDIR%\qthelp\complexity.qhcp echo.To view the help file: echo.^> assistant -collectionFile %BUILDDIR%\qthelp\complexity.ghc goto end ) if "%1" == "devhelp" ( %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp if errorlevel 1 exit /b 1 echo. echo.Build finished. goto end ) if "%1" == "epub" ( %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub if errorlevel 1 exit /b 1 echo. echo.Build finished. The epub file is in %BUILDDIR%/epub. goto end ) if "%1" == "latex" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex if errorlevel 1 exit /b 1 echo. echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. goto end ) if "%1" == "latexpdf" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex cd %BUILDDIR%/latex make all-pdf cd %BUILDDIR%/.. echo. echo.Build finished; the PDF files are in %BUILDDIR%/latex. goto end ) if "%1" == "latexpdfja" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex cd %BUILDDIR%/latex make all-pdf-ja cd %BUILDDIR%/.. echo. echo.Build finished; the PDF files are in %BUILDDIR%/latex. goto end ) if "%1" == "text" ( %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text if errorlevel 1 exit /b 1 echo. echo.Build finished. The text files are in %BUILDDIR%/text. goto end ) if "%1" == "man" ( %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man if errorlevel 1 exit /b 1 echo. echo.Build finished. The manual pages are in %BUILDDIR%/man. goto end ) if "%1" == "texinfo" ( %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo if errorlevel 1 exit /b 1 echo. echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. goto end ) if "%1" == "gettext" ( %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale if errorlevel 1 exit /b 1 echo. echo.Build finished. The message catalogs are in %BUILDDIR%/locale. goto end ) if "%1" == "changes" ( %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes if errorlevel 1 exit /b 1 echo. echo.The overview file is in %BUILDDIR%/changes. goto end ) if "%1" == "linkcheck" ( %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck if errorlevel 1 exit /b 1 echo. echo.Link check complete; look for any errors in the above output ^ or in %BUILDDIR%/linkcheck/output.txt. goto end ) if "%1" == "doctest" ( %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest if errorlevel 1 exit /b 1 echo. echo.Testing of doctests in the sources finished, look at the ^ results in %BUILDDIR%/doctest/output.txt. goto end ) if "%1" == "xml" ( %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml if errorlevel 1 exit /b 1 echo. echo.Build finished. The XML files are in %BUILDDIR%/xml. goto end ) if "%1" == "pseudoxml" ( %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml if errorlevel 1 exit /b 1 echo. echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. goto end ) :endpython-flask-marshmallow-1.2.0/pyproject.toml000066400000000000000000000036211456021772300214020ustar00rootroot00000000000000[project] name = "flask-marshmallow" version = "1.2.0" description = "Flask + marshmallow for beautiful APIs" readme = "README.rst" license = { file = "LICENSE" } maintainers = [ { name = "Steven Loria", email = "sloria1@gmail.com" }, { name = "Stephen Rosen", email = "sirosen0@gmail.com" }, ] classifiers = [ "Environment :: Web Environment", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Natural Language :: English", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Topic :: Internet :: WWW/HTTP :: Dynamic Content", ] requires-python = ">=3.8" dependencies = ["Flask>=2.2", "marshmallow>=3.0.0"] [project.urls] Issues = "https://github.com/marshmallow-code/flask-marshmallow/issues" Funding = "https://opencollective.com/marshmallow" [project.optional-dependencies] docs = [ "marshmallow-sqlalchemy>=0.19.0", "Sphinx==7.2.6", "sphinx-issues==4.0.0", ] tests = ["flask-marshmallow[sqlalchemy]", "pytest"] dev = ["flask-marshmallow[tests]", "tox", "pre-commit~=3.5"] sqlalchemy = ["flask-sqlalchemy>=3.0.0", "marshmallow-sqlalchemy>=0.29.0"] [build-system] requires = ["flit_core<4"] build-backend = "flit_core.buildapi" [tool.flit.sdist] include = ["docs/", "tests/", "CHANGELOG.rst", "CONTRIBUTING.rst", "tox.ini"] exclude = ["docs/_build/"] [tool.ruff] src = ["src"] fix = true show-fixes = true show-source = true [tool.ruff.lint] select = [ "B", # flake8-bugbear "E", # pycodestyle error "F", # pyflakes "I", # isort "UP", # pyupgrade "W", # pycodestyle warning ] [tool.pytest.ini_options] filterwarnings = [ "error", "ignore:distutils Version classes are deprecated\\. Use packaging.version instead\\.:DeprecationWarning:marshmallow", ] python-flask-marshmallow-1.2.0/src/000077500000000000000000000000001456021772300172535ustar00rootroot00000000000000python-flask-marshmallow-1.2.0/src/flask_marshmallow/000077500000000000000000000000001456021772300227615ustar00rootroot00000000000000python-flask-marshmallow-1.2.0/src/flask_marshmallow/__init__.py000077500000000000000000000071021456021772300250750ustar00rootroot00000000000000""" flask_marshmallow ~~~~~~~~~~~~~~~~~ Integrates the marshmallow serialization/deserialization library with your Flask application. """ import typing import warnings from marshmallow import exceptions, pprint from marshmallow import fields as base_fields from . import fields from .schema import Schema if typing.TYPE_CHECKING: from flask import Flask has_sqla = False try: import flask_sqlalchemy # noqa: F401 except ImportError: has_sqla = False else: try: from . import sqla except ImportError: warnings.warn( "Flask-SQLAlchemy integration requires " "marshmallow-sqlalchemy to be installed.", stacklevel=2, ) else: has_sqla = True __all__ = [ "EXTENSION_NAME", "Marshmallow", "Schema", "fields", "exceptions", "pprint", ] EXTENSION_NAME = "flask-marshmallow" def _attach_fields(obj): """Attach all the marshmallow fields classes to ``obj``, including Flask-Marshmallow's custom fields. """ for attr in base_fields.__all__: if not hasattr(obj, attr): setattr(obj, attr, getattr(base_fields, attr)) for attr in fields.__all__: setattr(obj, attr, getattr(fields, attr)) class Marshmallow: """Wrapper class that integrates Marshmallow with a Flask application. To use it, instantiate with an application:: from flask import Flask app = Flask(__name__) ma = Marshmallow(app) The object provides access to the :class:`Schema` class, all fields in :mod:`marshmallow.fields`, as well as the Flask-specific fields in :mod:`flask_marshmallow.fields`. You can declare schema like so:: class BookSchema(ma.Schema): class Meta: fields = ('id', 'title', 'author', 'links') author = ma.Nested(AuthorSchema) links = ma.Hyperlinks({ 'self': ma.URLFor('book_detail', values=dict(id='')), 'collection': ma.URLFor('book_list') }) In order to integrate with Flask-SQLAlchemy, this extension must be initialized *after* `flask_sqlalchemy.SQLAlchemy`. :: db = SQLAlchemy(app) ma = Marshmallow(app) This gives you access to `ma.SQLAlchemySchema` and `ma.SQLAlchemyAutoSchema`, which generate marshmallow `~marshmallow.Schema` classes based on the passed in model or table. :: class AuthorSchema(ma.SQLAlchemyAutoSchema): class Meta: model = Author :param Flask app: The Flask application object. """ def __init__(self, app: typing.Optional["Flask"] = None): self.Schema = Schema if has_sqla: self.SQLAlchemySchema = sqla.SQLAlchemySchema self.SQLAlchemyAutoSchema = sqla.SQLAlchemyAutoSchema self.auto_field = sqla.auto_field self.HyperlinkRelated = sqla.HyperlinkRelated _attach_fields(self) if app is not None: self.init_app(app) def init_app(self, app: "Flask"): """Initializes the application with the extension. :param Flask app: The Flask application object. """ app.extensions = getattr(app, "extensions", {}) # If using Flask-SQLAlchemy, attach db.session to SQLAlchemySchema if has_sqla and "sqlalchemy" in app.extensions: db = app.extensions["sqlalchemy"] self.SQLAlchemySchema.OPTIONS_CLASS.session = db.session self.SQLAlchemyAutoSchema.OPTIONS_CLASS.session = db.session app.extensions[EXTENSION_NAME] = self python-flask-marshmallow-1.2.0/src/flask_marshmallow/fields.py000077500000000000000000000164761456021772300246220ustar00rootroot00000000000000""" flask_marshmallow.fields ~~~~~~~~~~~~~~~~~~~~~~~~ Custom, Flask-specific fields. See the `marshmallow.fields` module for the list of all fields available from the marshmallow library. """ import re import typing from flask import current_app, url_for from marshmallow import fields, missing __all__ = [ "URLFor", "UrlFor", "AbsoluteURLFor", "AbsoluteUrlFor", "Hyperlinks", "File", "Config", ] _tpl_pattern = re.compile(r"\s*<\s*(\S*)\s*>\s*") def _tpl(val: str) -> typing.Optional[str]: """Return value within ``< >`` if possible, else return ``None``.""" match = _tpl_pattern.match(val) if match: return match.groups()[0] return None def _get_value(obj, key, default=missing): """Slightly-modified version of marshmallow.utils.get_value. If a dot-delimited ``key`` is passed and any attribute in the path is `None`, return `None`. """ if "." in key: return _get_value_for_keys(obj, key.split("."), default) else: return _get_value_for_key(obj, key, default) def _get_value_for_keys(obj, keys, default): if len(keys) == 1: return _get_value_for_key(obj, keys[0], default) else: value = _get_value_for_key(obj, keys[0], default) # XXX This differs from the marshmallow implementation if value is None: return None return _get_value_for_keys(value, keys[1:], default) def _get_value_for_key(obj, key, default): if not hasattr(obj, "__getitem__"): return getattr(obj, key, default) try: return obj[key] except (KeyError, IndexError, TypeError, AttributeError): return getattr(obj, key, default) class URLFor(fields.Field): """Field that outputs the URL for an endpoint. Acts identically to Flask's ``url_for`` function, except that arguments can be pulled from the object to be serialized, and ``**values`` should be passed to the ``values`` parameter. Usage: :: url = URLFor('author_get', values=dict(id='')) https_url = URLFor( 'author_get', values=dict(id='', _scheme='https', _external=True), ) :param str endpoint: Flask endpoint name. :param dict values: Same keyword arguments as Flask's url_for, except string arguments enclosed in `< >` will be interpreted as attributes to pull from the object. :param kwargs: keyword arguments to pass to marshmallow field (e.g. ``required``). """ _CHECK_ATTRIBUTE = False def __init__( self, endpoint: str, values: typing.Optional[typing.Dict[str, typing.Any]] = None, **kwargs, ): self.endpoint = endpoint self.values = values or {} fields.Field.__init__(self, **kwargs) def _serialize(self, value, key, obj): """Output the URL for the endpoint, given the kwargs passed to ``__init__``. """ param_values = {} for name, attr_tpl in self.values.items(): attr_name = _tpl(str(attr_tpl)) if attr_name: attribute_value = _get_value(obj, attr_name, default=missing) if attribute_value is None: return None if attribute_value is not missing: param_values[name] = attribute_value else: raise AttributeError( f"{attr_name!r} is not a valid " f"attribute of {obj!r}" ) else: param_values[name] = attr_tpl return url_for(self.endpoint, **param_values) UrlFor = URLFor class AbsoluteURLFor(URLFor): """Field that outputs the absolute URL for an endpoint.""" def __init__( self, endpoint: str, values: typing.Optional[typing.Dict[str, typing.Any]] = None, **kwargs, ): if values: values["_external"] = True else: values = {"_external": True} URLFor.__init__(self, endpoint=endpoint, values=values, **kwargs) AbsoluteUrlFor = AbsoluteURLFor def _rapply( d: typing.Union[dict, typing.Iterable], func: typing.Callable, *args, **kwargs ): """Apply a function to all values in a dictionary or list of dictionaries, recursively. """ if isinstance(d, (tuple, list)): return [_rapply(each, func, *args, **kwargs) for each in d] if isinstance(d, dict): return {key: _rapply(value, func, *args, **kwargs) for key, value in d.items()} else: return func(d, *args, **kwargs) def _url_val(val: typing.Any, key: str, obj: typing.Any, **kwargs): """Function applied by `HyperlinksField` to get the correct value in the schema. """ if isinstance(val, URLFor): return val.serialize(key, obj, **kwargs) else: return val class Hyperlinks(fields.Field): """Field that outputs a dictionary of hyperlinks, given a dictionary schema with :class:`~flask_marshmallow.fields.URLFor` objects as values. Example: :: _links = Hyperlinks({ 'self': URLFor('author', values=dict(id='')), 'collection': URLFor('author_list'), }) `URLFor` objects can be nested within the dictionary. :: _links = Hyperlinks({ 'self': { 'href': URLFor('book', values=dict(id='')), 'title': 'book detail' } }) :param dict schema: A dict that maps names to :class:`~flask_marshmallow.fields.URLFor` fields. """ _CHECK_ATTRIBUTE = False def __init__(self, schema: typing.Dict[str, typing.Union[URLFor, str]], **kwargs): self.schema = schema fields.Field.__init__(self, **kwargs) def _serialize(self, value, attr, obj): return _rapply(self.schema, _url_val, key=attr, obj=obj) class File(fields.Field): """A binary file field for uploaded files. Examples: :: class ImageSchema(Schema): image = File(required=True) """ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # Metadata used by apispec self.metadata["type"] = "string" self.metadata["format"] = "binary" default_error_messages = {"invalid": "Not a valid file."} def _deserialize(self, value, attr, data, **kwargs): from werkzeug.datastructures import FileStorage if not isinstance(value, FileStorage): raise self.make_error("invalid") return value class Config(fields.Field): """A field for Flask configuration values. Examples: :: from flask import Flask app = Flask(__name__) app.config['API_TITLE'] = 'Pet API' class FooSchema(Schema): user = String() title = Config('API_TITLE') This field should only be used in an output schema. A ``ValueError`` will be raised if the config key is not found in the app config. :param str key: The key of the configuration value. """ _CHECK_ATTRIBUTE = False def __init__(self, key: str, **kwargs): fields.Field.__init__(self, **kwargs) self.key = key def _serialize(self, value, attr, obj, **kwargs): if self.key not in current_app.config: raise ValueError(f"The key {self.key!r} is not found in the app config.") return current_app.config[self.key] python-flask-marshmallow-1.2.0/src/flask_marshmallow/py.typed000066400000000000000000000000001456021772300244460ustar00rootroot00000000000000python-flask-marshmallow-1.2.0/src/flask_marshmallow/schema.py000066400000000000000000000026041456021772300245750ustar00rootroot00000000000000import typing import flask import marshmallow as ma if typing.TYPE_CHECKING: from flask.wrappers import Response class Schema(ma.Schema): """Base serializer with which to define custom serializers. See `marshmallow.Schema` for more details about the `Schema` API. """ def jsonify( self, obj: typing.Any, many: typing.Optional[bool] = None, *args, **kwargs ) -> "Response": """Return a JSON response containing the serialized data. :param obj: Object to serialize. :param bool many: Whether `obj` should be serialized as an instance or as a collection. If None, defaults to the value of the `many` attribute on this Schema. :param kwargs: Additional keyword arguments passed to `flask.jsonify`. .. versionchanged:: 0.6.0 Takes the same arguments as `marshmallow.Schema.dump`. Additional keyword arguments are passed to `flask.jsonify`. .. versionchanged:: 0.6.3 The `many` argument for this method defaults to the value of the `many` attribute on the Schema. Previously, the `many` argument of this method defaulted to False, regardless of the value of `Schema.many`. """ if many is None: many = self.many data = self.dump(obj, many=many) return flask.jsonify(data, *args, **kwargs) python-flask-marshmallow-1.2.0/src/flask_marshmallow/sqla.py000066400000000000000000000076031456021772300243010ustar00rootroot00000000000000""" flask_marshmallow.sqla ~~~~~~~~~~~~~~~~~~~~~~ Integration with Flask-SQLAlchemy and marshmallow-sqlalchemy. Provides `SQLAlchemySchema ` and `SQLAlchemyAutoSchema ` classes that use the scoped session from Flask-SQLAlchemy. """ from urllib import parse import marshmallow_sqlalchemy as msqla from flask import current_app, url_for from marshmallow.exceptions import ValidationError from .schema import Schema class DummySession: """Placeholder session object.""" pass class FlaskSQLAlchemyOptsMixin: session = DummySession() def __init__(self, meta, **kwargs): if not hasattr(meta, "sqla_session"): meta.sqla_session = self.session super().__init__(meta, **kwargs) # SQLAlchemySchema and SQLAlchemyAutoSchema are available in newer ma-sqla versions if hasattr(msqla, "SQLAlchemySchema"): class SQLAlchemySchemaOpts(FlaskSQLAlchemyOptsMixin, msqla.SQLAlchemySchemaOpts): pass class SQLAlchemySchema(msqla.SQLAlchemySchema, Schema): """SQLAlchemySchema that associates a schema with a model via the `model` class Meta option, which should be a ``db.Model`` class from `flask_sqlalchemy`. Uses the scoped session from Flask-SQLAlchemy by default. See `marshmallow_sqlalchemy.SQLAlchemySchema` for more details on the `SQLAlchemySchema` API. """ OPTIONS_CLASS = SQLAlchemySchemaOpts else: SQLAlchemySchema = None # type: ignore if hasattr(msqla, "SQLAlchemyAutoSchema"): class SQLAlchemyAutoSchemaOpts( FlaskSQLAlchemyOptsMixin, msqla.SQLAlchemyAutoSchemaOpts ): pass class SQLAlchemyAutoSchema(msqla.SQLAlchemyAutoSchema, Schema): """SQLAlchemyAutoSchema that automatically generates marshmallow fields from a SQLAlchemy model's or table's column. Uses the scoped session from Flask-SQLAlchemy by default. See `marshmallow_sqlalchemy.SQLAlchemyAutoSchema` for more details on the `SQLAlchemyAutoSchema` API. """ OPTIONS_CLASS = SQLAlchemyAutoSchemaOpts else: SQLAlchemyAutoSchema = None # type: ignore auto_field = getattr(msqla, "auto_field", None) class HyperlinkRelated(msqla.fields.Related): """Field that generates hyperlinks to indicate references between models, rather than primary keys. :param str endpoint: Flask endpoint name for generated hyperlink. :param str url_key: The attribute containing the reference's primary key. Defaults to "id". :param bool external: Set to `True` if absolute URLs should be used, instead of relative URLs. """ def __init__( self, endpoint: str, url_key: str = "id", external: bool = False, **kwargs ): super().__init__(**kwargs) self.endpoint = endpoint self.url_key = url_key self.external = external def _serialize(self, value, attr, obj): if value is None: return None key = super()._serialize(value, attr, obj) kwargs = {self.url_key: key} return url_for(self.endpoint, _external=self.external, **kwargs) def _deserialize(self, value, *args, **kwargs): if self.external: parsed = parse.urlparse(value) value = parsed.path endpoint, kwargs = self.adapter.match(value) if endpoint != self.endpoint: raise ValidationError( f'Parsed endpoint "{endpoint}" from URL "{value}"; expected ' f'"{self.endpoint}"' ) if self.url_key not in kwargs: raise ValidationError( f'URL pattern "{self.url_key}" not found in {kwargs!r}' ) return super()._deserialize(kwargs[self.url_key], *args, **kwargs) @property def adapter(self): return current_app.url_map.bind("") python-flask-marshmallow-1.2.0/src/flask_marshmallow/validate.py000066400000000000000000000146451456021772300251360ustar00rootroot00000000000000""" flask_marshmallow.validate ~~~~~~~~~~~~~~~~~~~~~~~~~~ Custom validation classes for various types of data. """ import os import re import typing from marshmallow.exceptions import ValidationError from marshmallow.validate import Validator as Validator from werkzeug.datastructures import FileStorage def _get_filestorage_size(file: FileStorage) -> int: """Return the size of the FileStorage object in bytes.""" size: int = file.stream.getbuffer().nbytes # type: ignore return size # This function is copied from loguru with few modifications. # https://github.com/Delgan/loguru/blob/master/loguru/_string_parsers.py#L35 def _parse_size(size: str) -> float: """Return the value which the ``size`` represents in bytes.""" size = size.strip() reg = re.compile(r"([e\+\-\.\d]+)\s*([kmgtpezy])?(i)?(b)", flags=re.I) match = reg.fullmatch(size) if not match: raise ValueError(f"Invalid size value: '{size!r}'") s, u, i, b = match.groups() try: s = float(s) except ValueError as e: raise ValueError(f"Invalid float value while parsing size: '{s!r}'") from e u = "kmgtpezy".index(u.lower()) + 1 if u else 0 i = 1024 if i else 1000 b = {"b": 8, "B": 1}[b] if b else 1 return s * i**u / b class FileSize(Validator): """Validator which succeeds if the file passed to it is within the specified size range. If ``min`` is not specified, or is specified as `None`, no lower bound exists. If ``max`` is not specified, or is specified as `None`, no upper bound exists. The inclusivity of the bounds (if they exist) is configurable. If ``min_inclusive`` is not specified, or is specified as `True`, then the ``min`` bound is included in the range. If ``max_inclusive`` is not specified, or is specified as `True`, then the ``max`` bound is included in the range. Example: :: class ImageSchema(Schema): image = File(required=True, validate=FileSize(min='1 MiB', max='2 MiB')) :param min: The minimum size (lower bound). If not provided, minimum size will not be checked. :param max: The maximum size (upper bound). If not provided, maximum size will not be checked. :param min_inclusive: Whether the ``min`` bound is included in the range. :param max_inclusive: Whether the ``max`` bound is included in the range. :param error: Error message to raise in case of a validation error. Can be interpolated with `{input}`, `{min}` and `{max}`. """ message_min = "Must be {min_op} {{min}}." message_max = "Must be {max_op} {{max}}." message_all = "Must be {min_op} {{min}} and {max_op} {{max}}." message_gte = "greater than or equal to" message_gt = "greater than" message_lte = "less than or equal to" message_lt = "less than" def __init__( self, min: typing.Optional[str] = None, max: typing.Optional[str] = None, min_inclusive: bool = True, max_inclusive: bool = True, error: typing.Optional[str] = None, ): self.min = min self.max = max self.min_size = _parse_size(self.min) if self.min else None self.max_size = _parse_size(self.max) if self.max else None self.min_inclusive = min_inclusive self.max_inclusive = max_inclusive self.error = error self.message_min = self.message_min.format( min_op=self.message_gte if self.min_inclusive else self.message_gt ) self.message_max = self.message_max.format( max_op=self.message_lte if self.max_inclusive else self.message_lt ) self.message_all = self.message_all.format( min_op=self.message_gte if self.min_inclusive else self.message_gt, max_op=self.message_lte if self.max_inclusive else self.message_lt, ) def _repr_args(self): return "min={!r}, max={!r}, min_inclusive={!r}, max_inclusive={!r}".format( self.min, self.max, self.min_inclusive, self.max_inclusive ) def _format_error(self, value, message): return (self.error or message).format(input=value, min=self.min, max=self.max) def __call__(self, value): if not isinstance(value, FileStorage): raise TypeError( f"A FileStorage object is required, not {type(value).__name__!r}" ) file_size = _get_filestorage_size(value) if self.min_size is not None and ( file_size < self.min_size if self.min_inclusive else file_size <= self.min_size ): message = self.message_min if self.max is None else self.message_all raise ValidationError(self._format_error(value, message)) if self.max_size is not None and ( file_size > self.max_size if self.max_inclusive else file_size >= self.max_size ): message = self.message_max if self.min is None else self.message_all raise ValidationError(self._format_error(value, message)) return value class FileType(Validator): """Validator which succeeds if the uploaded file is allowed by a given list of extensions. Example: :: class ImageSchema(Schema): image = File(required=True, validate=FileType(['.png'])) :param accept: A sequence of allowed extensions. :param error: Error message to raise in case of a validation error. Can be interpolated with ``{input}`` and ``{extensions}``. """ default_message = "Not an allowed file type. Allowed file types: [{extensions}]" def __init__( self, accept: typing.Iterable[str], error: typing.Optional[str] = None, ): self.allowed_types = {ext.lower() for ext in accept} self.error = error or self.default_message def _format_error(self, value): return (self.error or self.default_message).format( input=value, extensions="".join(self.allowed_types) ) def __call__(self, value): if not isinstance(value, FileStorage): raise TypeError( f"A FileStorage object is required, not {type(value).__name__!r}" ) _, extension = ( os.path.splitext(value.filename) if value.filename else (None, None) ) if extension is None or extension.lower() not in self.allowed_types: raise ValidationError(self._format_error(value)) return value python-flask-marshmallow-1.2.0/tests/000077500000000000000000000000001456021772300176265ustar00rootroot00000000000000python-flask-marshmallow-1.2.0/tests/__init__.py000066400000000000000000000000001456021772300217250ustar00rootroot00000000000000python-flask-marshmallow-1.2.0/tests/conftest.py000066400000000000000000000041571456021772300220340ustar00rootroot00000000000000"""Pytest fixtures for the test suite.""" import pytest from flask import Flask from flask_marshmallow import Marshmallow _app = Flask(__name__) _app.testing = True class Bunch(dict): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.__dict__ = self # Models class Author(Bunch): pass class Book(Bunch): pass @pytest.fixture def mockauthor(): author = Author(id=123, name="Fred Douglass") return author @pytest.fixture def mockauthorlist(): a1 = Author(id=1, name="Alice") a2 = Author(id=2, name="Bob") a3 = Author(id=3, name="Carol") return [a1, a2, a3] @pytest.fixture def mockbook(mockauthor): book = Book(id=42, author=mockauthor, title="Legend of Bagger Vance") return book @_app.route("/author/") def author(id): return "Steven Pressfield" @_app.route("/authors/") def authors(): return "Steven Pressfield, Chuck Paluhniuk" @_app.route("/books/") def books(): return "Legend of Bagger Vance, Fight Club" @_app.route("/books/") def book(id): return "Legend of Bagger Vance" @pytest.fixture(scope="function") def app(): ctx = _app.test_request_context() ctx.push() yield _app ctx.pop() @pytest.fixture(scope="function") def ma(app): return Marshmallow(app) @pytest.fixture def schemas(ma): class AuthorSchema(ma.Schema): class Meta: fields = ("id", "name", "absolute_url", "links") absolute_url = ma.AbsoluteURLFor("author", values={"id": ""}) links = ma.Hyperlinks( { "self": ma.URLFor("author", values={"id": ""}), "collection": ma.URLFor("authors"), } ) class BookSchema(ma.Schema): class Meta: fields = ("id", "title", "author", "links") author = ma.Nested(AuthorSchema) links = ma.Hyperlinks( { "self": ma.URLFor("book", values={"id": ""}), "collection": ma.URLFor("books"), } ) # So we can access schemas.AuthorSchema, etc. return Bunch(**locals()) python-flask-marshmallow-1.2.0/tests/test_core.py000077500000000000000000000036741456021772300222040ustar00rootroot00000000000000import json from flask import Flask, url_for from werkzeug.wrappers import Response from flask_marshmallow import Marshmallow def test_deferred_initialization(): app = Flask(__name__) m = Marshmallow() m.init_app(app) assert "flask-marshmallow" in app.extensions def test_schema(app, schemas, mockauthor): s = schemas.AuthorSchema() result = s.dump(mockauthor) assert result["id"] == mockauthor.id assert result["name"] == mockauthor.name assert result["absolute_url"] == url_for("author", id=mockauthor.id, _external=True) links = result["links"] assert links["self"] == url_for("author", id=mockauthor.id) assert links["collection"] == url_for("authors") def test_jsonify_instance(app, schemas, mockauthor): s = schemas.AuthorSchema() resp = s.jsonify(mockauthor) assert isinstance(resp, Response) assert resp.content_type == "application/json" obj = json.loads(resp.get_data(as_text=True)) assert isinstance(obj, dict) def test_jsonify_collection(app, schemas, mockauthorlist): s = schemas.AuthorSchema() resp = s.jsonify(mockauthorlist, many=True) assert isinstance(resp, Response) assert resp.content_type == "application/json" obj = json.loads(resp.get_data(as_text=True)) assert isinstance(obj, list) def test_jsonify_collection_via_schema_attr(app, schemas, mockauthorlist): s = schemas.AuthorSchema(many=True) resp = s.jsonify(mockauthorlist) assert isinstance(resp, Response) assert resp.content_type == "application/json" obj = json.loads(resp.get_data(as_text=True)) assert isinstance(obj, list) def test_links_within_nested_object(app, schemas, mockbook): s = schemas.BookSchema() result = s.dump(mockbook) assert result["title"] == mockbook.title author = result["author"] assert author["links"]["self"] == url_for("author", id=mockbook.author.id) assert author["links"]["collection"] == url_for("authors") python-flask-marshmallow-1.2.0/tests/test_fields.py000066400000000000000000000120711456021772300225060ustar00rootroot00000000000000import io import pytest from flask import url_for from marshmallow.exceptions import ValidationError from werkzeug.datastructures import FileStorage from werkzeug.routing import BuildError from flask_marshmallow.fields import _tpl @pytest.mark.parametrize( "template", ["", " ", " ", "< id>", "", "< id >"] ) def test_tpl(template): assert _tpl(template) == "id" assert _tpl(template) == "id" assert _tpl(template) == "id" def test_url_field(ma, mockauthor): field = ma.URLFor("author", values=dict(id="")) result = field.serialize("url", mockauthor) assert result == url_for("author", id=mockauthor.id) mockauthor.id = 0 result = field.serialize("url", mockauthor) assert result == url_for("author", id=0) def test_url_field_with_invalid_attribute(ma, mockauthor): field = ma.URLFor("author", values=dict(id="")) expected_msg = "{!r} is not a valid attribute of {!r}".format( "not-an-attr", mockauthor ) with pytest.raises(AttributeError, match=expected_msg): field.serialize("url", mockauthor) def test_url_field_handles_nested_attribute(ma, mockbook, mockauthor): field = ma.URLFor("author", values=dict(id="")) result = field.serialize("url", mockbook) assert result == url_for("author", id=mockauthor.id) def test_url_field_handles_none_attribute(ma, mockbook, mockauthor): mockbook.author = None field = ma.URLFor("author", values=dict(id="")) result = field.serialize("url", mockbook) assert result is None field = ma.URLFor("author", values=dict(id="")) result = field.serialize("url", mockbook) assert result is None def test_url_field_deserialization(ma): field = ma.URLFor("author", values=dict(id=""), allow_none=True) # noop assert field.deserialize("foo") == "foo" assert field.deserialize(None) is None def test_invalid_endpoint_raises_build_error(ma, mockauthor): field = ma.URLFor("badendpoint") with pytest.raises(BuildError): field.serialize("url", mockauthor) def test_hyperlinks_field(ma, mockauthor): field = ma.Hyperlinks( { "self": ma.URLFor("author", values={"id": ""}), "collection": ma.URLFor("authors"), } ) result = field.serialize("_links", mockauthor) assert result == { "self": url_for("author", id=mockauthor.id), "collection": url_for("authors"), } def test_hyperlinks_field_recurses(ma, mockauthor): field = ma.Hyperlinks( { "self": { "href": ma.URLFor("author", values={"id": ""}), "title": "The author", }, "collection": {"href": ma.URLFor("authors"), "title": "Authors list"}, } ) result = field.serialize("_links", mockauthor) assert result == { "self": {"href": url_for("author", id=mockauthor.id), "title": "The author"}, "collection": {"href": url_for("authors"), "title": "Authors list"}, } def test_hyperlinks_field_recurses_into_list(ma, mockauthor): field = ma.Hyperlinks( [ {"rel": "self", "href": ma.URLFor("author", values={"id": ""})}, {"rel": "collection", "href": ma.URLFor("authors")}, ] ) result = field.serialize("_links", mockauthor) assert result == [ {"rel": "self", "href": url_for("author", id=mockauthor.id)}, {"rel": "collection", "href": url_for("authors")}, ] def test_hyperlinks_field_deserialization(ma): field = ma.Hyperlinks( {"href": ma.URLFor("author", values={"id": ""})}, allow_none=True ) # noop assert field.deserialize("/author") == "/author" assert field.deserialize(None) is None def test_absolute_url(ma, mockauthor): field = ma.AbsoluteURLFor("authors") result = field.serialize("abs_url", mockauthor) assert result == url_for("authors", _external=True) def test_absolute_url_deserialization(ma): field = ma.AbsoluteURLFor("authors", allow_none=True) assert field.deserialize("foo") == "foo" assert field.deserialize(None) is None def test_aliases(ma): from flask_marshmallow.fields import AbsoluteURLFor, AbsoluteUrlFor, URLFor, UrlFor assert UrlFor is URLFor assert AbsoluteUrlFor is AbsoluteURLFor def test_file_field(ma, mockauthor): field = ma.File() fs = FileStorage(io.BytesIO(b"test"), "test.jpg") result = field.deserialize(fs, mockauthor) assert result == fs with pytest.raises(ValidationError, match="Field may not be null."): field.deserialize(None, mockauthor) with pytest.raises(ValidationError, match="Not a valid file."): field.deserialize("123", mockauthor) def test_config_field(ma, app, mockauthor): app.config["NAME"] = "test" field = ma.Config(key="NAME") result = field.serialize("config_value", mockauthor) assert result == "test" field = ma.Config(key="DOES_NOT_EXIST") with pytest.raises(ValueError, match="not found in the app config"): field.serialize("config_value", mockauthor) python-flask-marshmallow-1.2.0/tests/test_io.py000066400000000000000000000000001456021772300216340ustar00rootroot00000000000000python-flask-marshmallow-1.2.0/tests/test_sqla.py000066400000000000000000000221231456021772300221770ustar00rootroot00000000000000import pytest from flask import Flask, url_for from flask_sqlalchemy import SQLAlchemy from marshmallow import ValidationError from werkzeug.wrappers import Response from flask_marshmallow import Marshmallow from flask_marshmallow.sqla import HyperlinkRelated from tests.conftest import Bunch try: from marshmallow_sqlalchemy import SQLAlchemySchema # noqa: F401 except ImportError: has_sqlalchemyschema = False else: has_sqlalchemyschema = True requires_sqlalchemyschema = pytest.mark.skipif( not has_sqlalchemyschema, reason="SQLAlchemySchema not available" ) class TestSQLAlchemy: @pytest.fixture def extapp(self): app_ = Flask("extapp") app_.testing = True app_.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///:memory:" app_.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False SQLAlchemy(app_) Marshmallow(app_) @app_.route("/author/") def author(id): return f"...view for author {id}..." @app_.route("/book/") def book(id): return f"...view for book {id}..." ctx = app_.test_request_context() ctx.push() yield app_ ctx.pop() @pytest.fixture def db(self, extapp): return extapp.extensions["sqlalchemy"] @pytest.fixture def extma(self, extapp): return extapp.extensions["flask-marshmallow"] @pytest.fixture def models(self, db): class AuthorModel(db.Model): __tablename__ = "author" id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(255)) @property def url(self): return url_for("author", id=self.id) @property def absolute_url(self): return url_for("author", id=self.id, _external=True) class BookModel(db.Model): __tablename__ = "book" id = db.Column(db.Integer, primary_key=True) title = db.Column(db.String(255)) author_id = db.Column(db.Integer, db.ForeignKey("author.id")) author = db.relationship("AuthorModel", backref="books") @property def url(self): return url_for("book", id=self.id) @property def absolute_url(self): return url_for("book", id=self.id, _external=True) db.create_all() yield Bunch(Author=AuthorModel, Book=BookModel) db.drop_all() def test_can_initialize_extensions(self, extapp): assert "flask-marshmallow" in extapp.extensions assert "sqlalchemy" in extapp.extensions @requires_sqlalchemyschema def test_can_declare_sqla_schemas(self, extma, models, db): class AuthorSchema(extma.SQLAlchemySchema): class Meta: model = models.Author id = extma.auto_field() name = extma.auto_field() class BookSchema(extma.SQLAlchemySchema): class Meta: model = models.Book id = extma.auto_field() title = extma.auto_field() author_id = extma.auto_field() author_schema = AuthorSchema() book_schema = BookSchema() author = models.Author(name="Chuck Paluhniuk") book = models.Book(title="Fight Club", author=author) author_result = author_schema.dump(author) assert "id" in author_result assert "name" in author_result assert author_result["id"] == author.id assert author_result["name"] == "Chuck Paluhniuk" book_result = book_schema.dump(book) assert "id" in book_result assert "title" in book_result assert book_result["id"] == book.id assert book_result["title"] == book.title assert book_result["author_id"] == book.author_id resp = author_schema.jsonify(author) assert isinstance(resp, Response) @requires_sqlalchemyschema def test_can_declare_sqla_auto_schemas(self, extma, models, db): class AuthorSchema(extma.SQLAlchemyAutoSchema): class Meta: model = models.Author class BookSchema(extma.SQLAlchemyAutoSchema): class Meta: model = models.Book include_fk = True id = extma.auto_field() title = extma.auto_field() author_id = extma.auto_field() author_schema = AuthorSchema() book_schema = BookSchema() author = models.Author(name="Chuck Paluhniuk") book = models.Book(title="Fight Club", author=author) author_result = author_schema.dump(author) assert "id" in author_result assert "name" in author_result assert author_result["id"] == author.id assert author_result["name"] == "Chuck Paluhniuk" book_result = book_schema.dump(book) assert "id" in book_result assert "title" in book_result assert book_result["id"] == book.id assert book_result["title"] == book.title assert book_result["author_id"] == book.author_id resp = author_schema.jsonify(author) assert isinstance(resp, Response) # FIXME: temporarily filter out this warning # this is triggered by marshmallow-sqlalchemy on sqlalchemy v1.4.x # on the current version it should be fixed # in an upcoming marshmallow-sqlalchemy release @requires_sqlalchemyschema def test_hyperlink_related_field(self, extma, models, db, extapp): class BookSchema(extma.SQLAlchemySchema): class Meta: model = models.Book author = extma.HyperlinkRelated("author") book_schema = BookSchema() author = models.Author(name="Chuck Paluhniuk") book = models.Book(title="Fight Club", author=author) db.session.add(author) db.session.add(book) db.session.flush() book_result = book_schema.dump(book) assert book_result["author"] == author.url deserialized = book_schema.load(book_result) assert deserialized["author"] == author @requires_sqlalchemyschema def test_hyperlink_related_field_serializes_none(self, extma, models): class BookSchema(extma.SQLAlchemySchema): class Meta: model = models.Book author = extma.HyperlinkRelated("author") book_schema = BookSchema() book = models.Book(title="Fight Club", author=None) book_result = book_schema.dump(book) assert book_result["author"] is None @requires_sqlalchemyschema def test_hyperlink_related_field_errors(self, extma, models, db, extapp): class BookSchema(extma.SQLAlchemySchema): class Meta: model = models.Book author = HyperlinkRelated("author") book_schema = BookSchema() author = models.Author(name="Chuck Paluhniuk") book = models.Book(title="Fight Club", author=author) db.session.add(author) db.session.add(book) db.session.flush() # Deserialization fails on bad endpoint book_result = book_schema.dump(book) book_result["author"] = book.url with pytest.raises(ValidationError) as excinfo: book_schema.load(book_result) errors = excinfo.value.messages assert 'expected "author"' in errors["author"][0] # Deserialization fails on bad URL key book_result = book_schema.dump(book) book_schema.fields["author"].url_key = "pk" with pytest.raises(ValidationError) as excinfo: book_schema.load(book_result) errors = excinfo.value.messages assert 'URL pattern "pk" not found' in errors["author"][0] @requires_sqlalchemyschema def test_hyperlink_related_field_external(self, extma, models, db, extapp): class BookSchema(extma.SQLAlchemySchema): class Meta: model = models.Book author = HyperlinkRelated("author", external=True) book_schema = BookSchema() author = models.Author(name="Chuck Paluhniuk") book = models.Book(title="Fight Club", author=author) db.session.add(author) db.session.add(book) db.session.flush() book_result = book_schema.dump(book) assert book_result["author"] == author.absolute_url deserialized = book_schema.load(book_result) assert deserialized["author"] == author @requires_sqlalchemyschema def test_hyperlink_related_field_list(self, extma, models, db, extapp): class AuthorSchema(extma.SQLAlchemySchema): class Meta: model = models.Author books = extma.List(HyperlinkRelated("book")) author_schema = AuthorSchema() author = models.Author(name="Chuck Paluhniuk") book = models.Book(title="Fight Club", author=author) db.session.add(author) db.session.add(book) db.session.flush() author_result = author_schema.dump(author) assert author_result["books"][0] == book.url deserialized = author_schema.load(author_result) assert deserialized["books"][0] == book python-flask-marshmallow-1.2.0/tests/test_validate.py000066400000000000000000000124601456021772300230330ustar00rootroot00000000000000import io import pytest from marshmallow.exceptions import ValidationError from werkzeug.datastructures import FileStorage from flask_marshmallow import validate @pytest.mark.parametrize("size", ["1 KB", "1 KiB", "1 MB", "1 MiB", "1 GB", "1 GiB"]) def test_parse_size(size): rv = validate._parse_size(size) if size == "1 KB": assert rv == 1000 elif size == "1 KiB": assert rv == 1024 elif size == "1 MB": assert rv == 1000000 elif size == "1 MiB": assert rv == 1048576 elif size == "1 GB": assert rv == 1000000000 elif size == "1 GiB": assert rv == 1073741824 def test_get_filestorage_size(): rv = validate._get_filestorage_size(FileStorage(io.BytesIO(b"".ljust(0)))) assert rv == 0 rv = validate._get_filestorage_size(FileStorage(io.BytesIO(b"".ljust(123)))) assert rv == 123 rv = validate._get_filestorage_size(FileStorage(io.BytesIO(b"".ljust(1024)))) assert rv == 1024 rv = validate._get_filestorage_size(FileStorage(io.BytesIO(b"".ljust(1234)))) assert rv == 1234 @pytest.mark.parametrize("size", ["wrong_format", "1.2.3 MiB"]) def test_parse_size_wrong_value(size): if size == "wrong_format": with pytest.raises(ValueError, match="Invalid size value: "): validate._parse_size(size) elif size == "1.2.3 MiB": with pytest.raises( ValueError, match="Invalid float value while parsing size: " ): validate._parse_size(size) def test_filesize_min(): fs = FileStorage(io.BytesIO(b"".ljust(1024))) assert validate.FileSize(min="1 KiB", max="2 KiB")(fs) is fs assert validate.FileSize(min="0 KiB", max="1 KiB")(fs) is fs assert validate.FileSize()(fs) is fs assert validate.FileSize(min_inclusive=False, max_inclusive=False)(fs) is fs assert validate.FileSize(min="1 KiB", max="1 KiB")(fs) is fs with pytest.raises(ValidationError, match="Must be greater than or equal to 2 KiB"): validate.FileSize(min="2 KiB", max="3 KiB")(fs) with pytest.raises(ValidationError, match="Must be greater than or equal to 2 KiB"): validate.FileSize(min="2 KiB")(fs) with pytest.raises(ValidationError, match="Must be greater than 1 KiB"): validate.FileSize( min="1 KiB", max="2 KiB", min_inclusive=False, max_inclusive=True )(fs) with pytest.raises(ValidationError, match="less than 1 KiB"): validate.FileSize( min="1 KiB", max="1 KiB", min_inclusive=True, max_inclusive=False )(fs) def test_filesize_max(): fs = FileStorage(io.BytesIO(b"".ljust(2048))) assert validate.FileSize(min="1 KiB", max="2 KiB")(fs) is fs assert validate.FileSize(max="2 KiB")(fs) is fs assert validate.FileSize()(fs) is fs assert validate.FileSize(min_inclusive=False, max_inclusive=False)(fs) is fs assert validate.FileSize(min="2 KiB", max="2 KiB")(fs) is fs with pytest.raises(ValidationError, match="less than or equal to 1 KiB"): validate.FileSize(min="0 KiB", max="1 KiB")(fs) with pytest.raises(ValidationError, match="less than or equal to 1 KiB"): validate.FileSize(max="1 KiB")(fs) with pytest.raises(ValidationError, match="less than 2 KiB"): validate.FileSize( min="1 KiB", max="2 KiB", min_inclusive=True, max_inclusive=False )(fs) with pytest.raises(ValidationError, match="greater than 2 KiB"): validate.FileSize( min="2 KiB", max="2 KiB", min_inclusive=False, max_inclusive=True )(fs) def test_filesize_repr(): assert ( repr( validate.FileSize( min=None, max=None, error=None, min_inclusive=True, max_inclusive=True ) ) == "" # noqa: E501 ) assert ( repr( validate.FileSize( min="1 KiB", max="3 KiB", error="foo", min_inclusive=False, max_inclusive=False, ) ) == "" # noqa: E501 ) def test_filesize_wrongtype(): with pytest.raises(TypeError, match="A FileStorage object is required, not "): validate.FileSize()(1) def test_filetype(): png_fs = FileStorage(io.BytesIO(b"".ljust(1024)), "test.png") assert validate.FileType([".png"])(png_fs) is png_fs assert validate.FileType([".PNG"])(png_fs) is png_fs PNG_fs = FileStorage(io.BytesIO(b"".ljust(1024)), "test.PNG") assert validate.FileType([".png"])(PNG_fs) is PNG_fs assert validate.FileType([".PNG"])(PNG_fs) is PNG_fs with pytest.raises(TypeError, match="A FileStorage object is required, not "): validate.FileType([".png"])(1) with pytest.raises( ValidationError, match=r"Not an allowed file type. Allowed file types: \[.*?\]", # noqa: W605 ): jpg_fs = FileStorage(io.BytesIO(b"".ljust(1024)), "test.jpg") validate.FileType([".png"])(jpg_fs) with pytest.raises( ValidationError, match=r"Not an allowed file type. Allowed file types: \[.*?\]", # noqa: W605 ): no_ext_fs = FileStorage(io.BytesIO(b"".ljust(1024)), "test") validate.FileType([".png"])(no_ext_fs) python-flask-marshmallow-1.2.0/tox.ini000066400000000000000000000017441456021772300200050ustar00rootroot00000000000000[tox] envlist= lint py{38,39,310,311,312} py312-marshmallowdev py38-lowest docs [testenv] extras = tests deps = marshmallowdev: https://github.com/marshmallow-code/marshmallow/archive/dev.tar.gz lowest: marshmallow==3.0.0 lowest: marshmallow-sqlalchemy==0.29.0 lowest: flask-sqlalchemy==3.0.0 lowest: flask==2.2 lowest: werkzeug==2.2.2 ; lowest version supported by marshmallow-sqlalchemy lowest: sqlalchemy==1.4.40 commands = pytest {posargs} [testenv:lint] deps = pre-commit~=3.5 skip_install = true commands = pre-commit run --all-files [testenv:docs] extras = docs commands = sphinx-build docs/ docs/_build {posargs} ; Below tasks are for development only (not run in CI) [testenv:watch-docs] deps = sphinx-autobuild extras = docs commands = sphinx-autobuild --open-browser docs/ docs/_build {posargs} --watch src/flask_marshmallow --delay 2 [testenv:watch-readme] deps = restview skip_install = true commands = restview README.rst