python-webargs_8.0.1.orig/.github/0000755000000000000000000000000013654272604014011 5ustar00python-webargs_8.0.1.orig/.gitignore0000644000000000000000000000227713326636200014441 0ustar00# Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging .Python build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ *.egg-info/ .installed.cfg *.egg MANIFEST # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover .hypothesis/ .pytest_cache/ # Translations *.mo *.pot # Django stuff: *.log local_settings.py db.sqlite3 # Flask stuff: instance/ .webassets-cache # Scrapy stuff: .scrapy # Sphinx documentation docs/_build/ README.html # PyBuilder target/ # Jupyter Notebook .ipynb_checkpoints # pyenv .python-version # celery beat schedule file celerybeat-schedule # SageMath parsed files *.sage.py # Environments .env .venv env/ venv/ ENV/ env.bak/ venv.bak/ # Spyder project settings .spyderproject .spyproject # Rope project settings .ropeproject # mkdocs documentation /site # mypy .mypy_cache/ python-webargs_8.0.1.orig/.pre-commit-config.yaml0000644000000000000000000000127114163275625016735 0ustar00repos: - repo: https://github.com/asottile/pyupgrade rev: v2.11.0 hooks: - id: pyupgrade args: ["--py36-plus"] - repo: https://github.com/psf/black rev: 20.8b1 hooks: - id: black - repo: https://gitlab.com/pycqa/flake8 rev: 3.9.0 hooks: - id: flake8 additional_dependencies: [flake8-bugbear==21.4.3] - repo: https://github.com/asottile/blacken-docs rev: v1.10.0 hooks: - id: blacken-docs additional_dependencies: [black==20.8b1] args: ["--target-version", "py35"] - repo: https://github.com/pre-commit/mirrors-mypy rev: v0.812 hooks: - id: mypy language_version: python3 files: ^src/webargs/ additional_dependencies: ["marshmallow>=3,<4"] python-webargs_8.0.1.orig/.readthedocs.yml0000644000000000000000000000025113775063555015544 0ustar00version: 2 sphinx: configuration: docs/conf.py formats: all python: version: 3.8 install: - method: pip path: . extra_requirements: - docs python-webargs_8.0.1.orig/AUTHORS.rst0000644000000000000000000000506514163275625014340 0ustar00======= Authors ======= Lead ---- * Steven Loria `@sloria `_ * Jérôme Lafréchoux `@lafrech `_ Contributors (chronological) ---------------------------- * Steven Manuatu `@venuatu `_ * Javier Santacruz `@jvrsantacruz `_ * Josh Carp `@jmcarp `_ * `@philtay `_ * Andriy Yurchuk `@Ch00k `_ * Stas Sușcov `@stas `_ * Josh Johnston `@Trii `_ * Rory Hart `@hartror `_ * Jace Browning `@jacebrowning `_ * marcellarius `@marcellarius `_ * Damian Heard `@DamianHeard `_ * Daniel Imhoff `@dwieeb `_ * `@immerrr `_ * Brett Higgins `@brettdh `_ * Vlad Frolov `@frol `_ * Tuukka Mustonen `@tuukkamustonen `_ * Francois-Xavier Darveau `@EFF `_ * Jérôme Lafréchoux `@lafrech `_ * `@DmitriyS `_ * Svetlozar Argirov `@zaro `_ * Florian S. `@nebularazer `_ * `@daniel98321 `_ * `@Itayazolay `_ * `@Reskov `_ * `@cedzz `_ * F. Moukayed (כוכב) `@kochab `_ * Xiaoyu Lee `@lee3164 `_ * Jonathan Angelo `@jangelo `_ * `@zhenhua32 `_ * Martin Roy `@lindycoder `_ * Kubilay Kocak `@koobs `_ * Stephen Rosen `@sirosen `_ * `@dodumosu `_ * Nate Dellinger `@Nateyo `_ * Karthikeyan Singaravelan `@tirkarthi `_ * Sami Salonen `@suola `_ * Tim Gates `@timgates42 `_ * Lefteris Karapetsas `@lefterisjp `_ * Utku Gultopu `@ugultopu `_ * Jason Williams `@jaswilli `_ * Grey Li `@greyli `_ * `@michaelizergit `_ * Legolas Bloom `@TTWShell `_ python-webargs_8.0.1.orig/CHANGELOG.rst0000644000000000000000000011423714163275625014504 0ustar00Changelog --------- 8.0.1 (2021-08-12) ****************** Bug fixes: * Fix "``DelimitedList`` deserializes empty string as ``['']``" (:issue:`623`). Thanks :user:`TTWSchell` for reporting and for the PR. Other changes: * New documentation theme with `furo`. Thanks to :user:`pradyunsg` for writing furo! * Webargs has a new logo. Thanks to :user:`michaelizergit`! (:issue:`312`) * Don't build universal wheels. We don't support Python 2 anymore. (:pr:`632`) * Make the build reproducible (:pr:`#631`). 8.0.0 (2021-04-08) ****************** Features: * Add `Parser.pre_load` as a method for allowing users to modify data before schema loading, but without redefining location loaders. See advanced docs on `Parser pre_load` for usage information. (:pr:`583`) * *Backwards-incompatible*: ``unknown`` defaults to `None` for body locations (`json`, `form` and `json_or_form`) (:issue:`580`). * Detection of fields as "multi-value" for unpacking lists from multi-dict types is now extensible with the ``is_multiple`` attribute. If a field sets ``is_multiple = True`` it will be detected as a multi-value field. If ``is_multiple`` is not set or is set to ``None``, webargs will check if the field is an instance of ``List`` or ``Tuple``. (:issue:`563`) * A new attribute on ``Parser`` objects, ``Parser.KNOWN_MULTI_FIELDS`` can be used to set fields which should be detected as ``is_multiple=True`` even when the attribute is not set (:pr:`592`). See docs on "Multi-Field Detection" for more details. Bug fixes: * ``Tuple`` field now behaves as a "multiple" field (:pr:`585`). 7.0.1 (2020-12-14) ****************** Bug fixes: * Fix `DelimitedList` and `DelimitedTuple` to pass additional keyword arguments through their `_serialize` methods to the child fields and fix type checking on these classes. (:issue:`569`) Thanks to :user:`decaz` for reporting. 7.0.0 (2020-12-10) ****************** Changes: * *Backwards-incompatible*: Drop support for webapp2 (:pr:`565`). * Add type annotations to `Parser` class, `DelimitedList`, and `DelimitedTuple`. (:issue:`566`) 7.0.0b2 (2020-12-01) ******************** Features: * `DjangoParser` now supports the `headers` location. (:issue:`540`) * `FalconParser` now supports a new `media` location, which uses Falcon's `media` decoding. (:issue:`253`) `media` behaves very similarly to the `json` location but also supports any registered media handler. See the `Falcon documentation on media types `_ for more details. Changes: * `FalconParser` defaults to the `media` location instead of `json`. (:issue:`253`) * Test against Python 3.9 (:pr:`552`). * *Backwards-incompatible*: Drop support for Python 3.5 (:pr:`553`). 7.0.0b1 (2020-09-11) ******************** Refactoring: * *Backwards-incompatible*: Remove support for marshmallow2 (:issue:`539`) * *Backwards-incompatible*: Remove `dict2schema` Users desiring the `dict2schema` functionality may now rely upon `marshmallow.Schema.from_dict`. Rewrite any code using `dict2schema` like so: .. code-block:: python import marshmallow as ma # webargs 6.x and older from webargs import dict2schema myschema = dict2schema({"q1", ma.fields.Int()}) # webargs 7.x myschema = ma.Schema.from_dict({"q1", ma.fields.Int()}) Features: * Add ``unknown`` as a parameter to ``Parser.parse``, ``Parser.use_args``, ``Parser.use_kwargs``, and parser instantiation. When set, it will be passed to ``Schema.load``. When not set, the value passed will depend on the parser's settings. If set to ``None``, the schema's default behavior will be used (i.e. no value is passed to ``Schema.load``) and parser settings will be ignored. This allows usages like .. code-block:: python import marshmallow as ma @parser.use_kwargs( {"q1": ma.fields.Int(), "q2": ma.fields.Int()}, location="query", unknown=ma.EXCLUDE ) def foo(q1, q2): ... * Defaults for ``unknown`` may be customized on parser classes via ``Parser.DEFAULT_UNKNOWN_BY_LOCATION``, which maps location names to values to use. Usages are varied, but include .. code-block:: python import marshmallow as ma from webargs.flaskparser import FlaskParser # as well as... class MyParser(FlaskParser): DEFAULT_UNKNOWN_BY_LOCATION = {"query": ma.INCLUDE} parser = MyParser() Setting the ``unknown`` value for a Parser instance has higher precedence. So .. code-block:: python parser = MyParser(unknown=ma.RAISE) will always pass ``RAISE``, even when the location is ``query``. * By default, webargs will pass ``unknown=EXCLUDE`` for all locations except for request bodies (``json``, ``form``, and ``json_or_form``) and path parameters. Request bodies and path parameters will pass ``unknown=RAISE``. This behavior is defined by the default value for ``DEFAULT_UNKNOWN_BY_LOCATION``. Changes: * Registered `error_handler` callbacks are required to raise an exception. If a handler is invoked and no exception is raised, `webargs` will raise a `ValueError` (:issue:`527`) 6.1.1 (2020-09-08) ****************** Bug fixes: * Failure to validate flask headers would produce error data which contained tuples as keys, and was therefore not JSON-serializable. (:issue:`500`) These errors will now extract the headername as the key correctly. Thanks to :user:`shughes-uk` for reporting. 6.1.0 (2020-04-05) ****************** Features: * Add ``fields.DelimitedTuple`` when using marshmallow 3. This behaves as a combination of ``fields.DelimitedList`` and ``marshmallow.fields.Tuple``. It takes an iterable of fields, plus a delimiter (defaults to ``,``), and parses delimiter-separated strings into tuples. (:pr:`509`) * Add ``__str__`` and ``__repr__`` to MultiDictProxy to make it easier to work with (:pr:`488`) Support: * Various docs updates (:pr:`482`, :pr:`486`, :pr:`489`, :pr:`498`, :pr:`508`). Thanks :user:`lefterisjp`, :user:`timgates42`, and :user:`ugultopu` for the PRs. 6.0.0 (2020-02-27) ****************** Features: * ``FalconParser``: Pass request content length to ``req.stream.read`` to provide compatibility with ``falcon.testing`` (:pr:`477`). Thanks :user:`suola` for the PR. * *Backwards-incompatible*: Factorize the ``use_args`` / ``use_kwargs`` branch in all parsers. When ``as_kwargs`` is ``False``, arguments are now consistently appended to the arguments list by the ``use_args`` decorator. Before this change, the ``PyramidParser`` would prepend the argument list on each call to ``use_args``. Pyramid view functions must reverse the order of their arguments. (:pr:`478`) 6.0.0b8 (2020-02-16) ******************** Refactoring: * *Backwards-incompatible*: Use keyword-only arguments (:pr:`472`). 6.0.0b7 (2020-02-14) ******************** Features: * *Backwards-incompatible*: webargs will rewrite the error messages in ValidationErrors to be namespaced under the location which raised the error. The `messages` field on errors will therefore be one layer deeper with a single top-level key. 6.0.0b6 (2020-01-31) ******************** Refactoring: * Remove the cache attached to webargs parsers. Due to changes between webargs v5 and v6, the cache is no longer considered useful. Other changes: * Import ``Mapping`` from ``collections.abc`` in pyramidparser.py (:pr:`471`). Thanks :user:`tirkarthi` for the PR. 6.0.0b5 (2020-01-30) ******************** Refactoring: * *Backwards-incompatible*: `DelimitedList` now requires that its input be a string and always serializes as a string. It can still serialize and deserialize using another field, e.g. `DelimitedList(Int())` is still valid and requires that the values in the list parse as ints. 6.0.0b4 (2020-01-28) ******************** Bug fixes: * :cve:`CVE-2020-7965`: Don't attempt to parse JSON if request's content type is mismatched (bugfix from 5.5.3). 6.0.0b3 (2020-01-21) ******************** Features: * *Backwards-incompatible*: Support Falcon 2.0. Drop support for Falcon 1.x (:pr:`459`). Thanks :user:`dodumosu` and :user:`Nateyo` for the PR. 6.0.0b2 (2020-01-07) ******************** Other changes: * *Backwards-incompatible*: Drop support for Python 2 (:issue:`440`). Thanks :user:`hugovk` for the PR. 6.0.0b1 (2020-01-06) ******************** Features: * *Backwards-incompatible*: Schemas will now load all data from a location, not only data specified by fields. As a result, schemas with validators which examine the full input data may change in behavior. The `unknown` parameter on schemas may be used to alter this. For example, `unknown=marshmallow.EXCLUDE` will produce a behavior similar to webargs v5. Bug fixes: * *Backwards-incompatible*: All parsers now require the Content-Type to be set correctly when processing JSON request bodies. This impacts ``DjangoParser``, ``FalconParser``, ``FlaskParser``, and ``PyramidParser`` Refactoring: * *Backwards-incompatible*: Schema fields may not specify a location any longer, and `Parser.use_args` and `Parser.use_kwargs` now accept `location` (singular) instead of `locations` (plural). Instead of using a single field or schema with multiple `locations`, users are recommended to make multiple calls to `use_args` or `use_kwargs` with a distinct schema per location. For example, code should be rewritten like this: .. code-block:: python # webargs 5.x and older @parser.use_args( { "q1": ma.fields.Int(location="query"), "q2": ma.fields.Int(location="query"), "h1": ma.fields.Int(location="headers"), }, locations=("query", "headers"), ) def foo(q1, q2, h1): ... # webargs 6.x @parser.use_args({"q1": ma.fields.Int(), "q2": ma.fields.Int()}, location="query") @parser.use_args({"h1": ma.fields.Int()}, location="headers") def foo(q1, q2, h1): ... * The `location_handler` decorator has been removed and replaced with `location_loader`. `location_loader` serves the same purpose (letting you write custom hooks for loading data) but its expected method signature is different. See the docs on `location_loader` for proper usage. Thanks :user:`sirosen` for the PR! 5.5.3 (2020-01-28) ****************** Bug fixes: * :cve:`CVE-2020-7965`: Don't attempt to parse JSON if request's content type is mismatched. 5.5.2 (2019-10-06) ****************** Bug fixes: * Handle ``UnicodeDecodeError`` when parsing JSON payloads (:issue:`427`). Thanks :user:`lindycoder` for the catch and patch. 5.5.1 (2019-09-15) ****************** Bug fixes: * Remove usage of deprecated ``Field.fail`` when using marshmallow 3. 5.5.0 (2019-09-07) ****************** Support: * Various docs updates (:pr:`414`, :pr:`421`). Refactoring: * Don't mutate ``globals()`` in ``webargs.fields`` (:pr:`411`). * Use marshmallow 3's ``Schema.from_dict`` if available (:pr:`415`). 5.4.0 (2019-07-23) ****************** Changes: * Use explicit type check for `fields.DelimitedList` when deciding to parse value with `getlist()` (`#406 (comment) `_ ). Support: * Add "Parsing Lists in Query Strings" section to docs (:issue:`406`). 5.3.2 (2019-06-19) ****************** Bug fixes: * marshmallow 3.0.0rc7 compatibility (:pr:`395`). 5.3.1 (2019-05-05) ****************** Bug fixes: * marshmallow 3.0.0rc6 compatibility (:pr:`384`). 5.3.0 (2019-04-08) ****************** Features: * Add `"path"` location to ``AIOHTTPParser``, ``FlaskParser``, and ``PyramidParser`` (:pr:`379`). Thanks :user:`zhenhua32` for the PR. * Add ``webargs.__version_info__``. 5.2.0 (2019-03-16) ****************** Features: * Make the schema class used when generating a schema from a dict overridable (:issue:`375`). Thanks :user:`ThiefMaster`. 5.1.3 (2019-03-11) ****************** Bug fixes: * :cve:`CVE-2019-9710`: Fix race condition between parallel requests when the cache is used (:issue:`371`). Thanks :user:`ThiefMaster` for reporting and fixing. 5.1.2 (2019-02-03) ****************** Bug fixes: * Remove lingering usages of ``ValidationError.status_code`` (:issue:`365`). Thanks :user:`decaz` for reporting. * Avoid ``AttributeError`` on Python<3.5.4 (:issue:`366`). * Fix incorrect type annotations for ``error_headers``. * Fix outdated docs (:issue:`367`). Thanks :user:`alexandersoto` for reporting. 5.1.1.post0 (2019-01-30) ************************ * Include LICENSE in sdist (:issue:`364`). 5.1.1 (2019-01-28) ****************** Bug fixes: * Fix installing ``simplejson`` on Python 2 by distributing a Python 2-only wheel (:issue:`363`). 5.1.0 (2019-01-11) ****************** Features: * Error handlers for `AsyncParser` classes may be coroutine functions. * Add type annotations to `AsyncParser` and `AIOHTTPParser`. Bug fixes: * Fix compatibility with Flask<1.0 (:issue:`355`). Thanks :user:`hoatle` for reporting. * Address warning on Python 3.7 about importing from ``collections.abc``. 5.0.0 (2019-01-03) ****************** Features: * *Backwards-incompatible*: A 400 HTTPError is raised when an invalid JSON payload is passed. (:issue:`329`). Thanks :user:`zedrdave` for reporting. Other changes: * *Backwards-incompatible*: `webargs.argmap2schema` is removed. Use `webargs.dict2schema` instead. * *Backwards-incompatible*: `webargs.ValidationError` is removed. Use `marshmallow.ValidationError` instead. .. code-block:: python # <5.0.0 from webargs import ValidationError def auth_validator(value): # ... raise ValidationError("Authentication failed", status_code=401) @use_args({"auth": fields.Field(validate=auth_validator)}) def auth_view(args): return jsonify(args) # >=5.0.0 from marshmallow import ValidationError def auth_validator(value): # ... raise ValidationError("Authentication failed") @use_args({"auth": fields.Field(validate=auth_validator)}, error_status_code=401) def auth_view(args): return jsonify(args) * *Backwards-incompatible*: Missing arguments will no longer be filled in when using ``@use_kwargs`` (:issue:`342,307,252`). Use ``**kwargs`` to account for non-required fields. .. code-block:: python # <5.0.0 @use_kwargs( {"first_name": fields.Str(required=True), "last_name": fields.Str(required=False)} ) def myview(first_name, last_name): # last_name is webargs.missing if it's missing from the request return {"first_name": first_name} # >=5.0.0 @use_kwargs( {"first_name": fields.Str(required=True), "last_name": fields.Str(required=False)} ) def myview(first_name, **kwargs): # last_name will not be in kwargs if it's missing from the request return {"first_name": first_name} * `simplejson `_ is now a required dependency on Python 2 (:pr:`334`). This ensures consistency of behavior across Python 2 and 3. 4.4.1 (2018-01-03) ****************** Bug fixes: * Remove usages of ``argmap2schema`` from ``fields.Nested``, ``AsyncParser``, and ``PyramidParser``. 4.4.0 (2019-01-03) ****************** * *Deprecation*: ``argmap2schema`` is deprecated in favor of ``dict2schema`` (:pr:`352`). 4.3.1 (2018-12-31) ****************** * Add ``force_all`` param to ``PyramidParser.use_args``. * Add warning about missing arguments to ``AsyncParser``. 4.3.0 (2018-12-30) ****************** * *Deprecation*: Add warning about missing arguments getting added to parsed arguments dictionary (:issue:`342`). This behavior will be removed in version 5.0.0. 4.2.0 (2018-12-27) ****************** Features: * Add ``force_all`` argument to ``use_args`` and ``use_kwargs`` (:issue:`252`, :issue:`307`). Thanks :user:`piroux` for reporting. * *Deprecation*: The ``status_code`` and ``headers`` arguments to ``ValidationError`` are deprecated. Pass ``error_status_code`` and ``error_headers`` to `Parser.parse`, `Parser.use_args`, and `Parser.use_kwargs` instead. (:issue:`327`, :issue:`336`). * Custom error handlers receive ``error_status_code`` and ``error_headers`` arguments. (:issue:`327`). .. code-block:: python # <4.2.0 @parser.error_handler def handle_error(error, req, schema): raise CustomError(error.messages) class MyParser(FlaskParser): def handle_error(self, error, req, schema): # ... raise CustomError(error.messages) # >=4.2.0 @parser.error_handler def handle_error(error, req, schema, status_code, headers): raise CustomError(error.messages) # OR @parser.error_handler def handle_error(error, **kwargs): raise CustomError(error.messages) class MyParser(FlaskParser): def handle_error(self, error, req, schema, status_code, headers): # ... raise CustomError(error.messages) # OR def handle_error(self, error, req, **kwargs): # ... raise CustomError(error.messages) Legacy error handlers will be supported until version 5.0.0. 4.1.3 (2018-12-02) ****************** Bug fixes: * Fix bug in ``AIOHTTParser`` that prevented calling ``use_args`` on the same view function multiple times (:issue:`273`). Thanks to :user:`dnp1` for reporting and :user:`jangelo` for the fix. * Fix compatibility with marshmallow 3.0.0rc1 (:pr:`330`). 4.1.2 (2018-11-03) ****************** Bug fixes: * Fix serialization behavior of ``DelimitedList`` (:pr:`319`). Thanks :user:`lee3164` for the PR. Other changes: * Test against Python 3.7. 4.1.1 (2018-10-25) ****************** Bug fixes: * Fix bug in ``AIOHTTPParser`` that caused a ``JSONDecode`` error when parsing empty payloads (:issue:`229`). Thanks :user:`explosic4` for reporting and thanks user :user:`kochab` for the PR. 4.1.0 (2018-09-17) ****************** Features: * Add ``webargs.testing`` module, which exposes ``CommonTestCase`` to third-party parser libraries (see comments in :pr:`287`). 4.0.0 (2018-07-15) ****************** Features: * *Backwards-incompatible*: Custom error handlers receive the `marshmallow.Schema` instance as the third argument. Update any functions decorated with `Parser.error_handler` to take a ``schema`` argument, like so: .. code-block:: python # 3.x @parser.error_handler def handle_error(error, req): raise CustomError(error.messages) # 4.x @parser.error_handler def handle_error(error, req, schema): raise CustomError(error.messages) See `marshmallow-code/marshmallow#840 (comment) `_ for more information about this change. Bug fixes: * *Backwards-incompatible*: Rename ``webargs.async`` to ``webargs.asyncparser`` to fix compatibility with Python 3.7 (:issue:`240`). Thanks :user:`Reskov` for the catch and patch. Other changes: * *Backwards-incompatible*: Drop support for Python 3.4 (:pr:`243`). Python 2.7 and >=3.5 are supported. * *Backwards-incompatible*: Drop support for marshmallow<2.15.0. marshmallow>=2.15.0 and >=3.0.0b12 are officially supported. * Use `black `_ with `pre-commit `_ for code formatting (:pr:`244`). 3.0.2 (2018-07-05) ****************** Bug fixes: * Fix compatibility with marshmallow 3.0.0b12 (:pr:`242`). Thanks :user:`lafrech`. 3.0.1 (2018-06-06) ****************** Bug fixes: * Respect `Parser.DEFAULT_VALIDATION_STATUS` when a `status_code` is not explicitly passed to `ValidationError` (:issue:`180`). Thanks :user:`foresmac` for finding this. Support: * Add "Returning HTTP 400 Responses" section to docs (:issue:`180`). 3.0.0 (2018-05-06) ****************** Changes: * *Backwards-incompatible*: Custom error handlers receive the request object as the second argument. Update any functions decorated with ``Parser.error_handler`` to take a `req` argument, like so: .. code-block:: python # 2.x @parser.error_handler def handle_error(error): raise CustomError(error.messages) # 3.x @parser.error_handler def handle_error(error, req): raise CustomError(error.messages) * *Backwards-incompatible*: Remove unused ``instance`` and ``kwargs`` arguments of ``argmap2schema``. * *Backwards-incompatible*: Remove ``Parser.load`` method (``Parser`` now calls ``Schema.load`` directly). These changes shouldn't affect most users. However, they might break custom parsers calling these methods. (:pr:`222`) * Drop support for aiohttp<3.0.0. 2.1.0 (2018-04-01) ****************** Features: * Respect ``data_key`` field argument (in marshmallow 3). Thanks :user:`lafrech`. 2.0.0 (2018-02-08) ****************** Changes: * Drop support for aiohttp<2.0.0. * Remove use of deprecated `Request.has_body` attribute in aiohttpparser (:issue:`186`). Thanks :user:`ariddell` for reporting. 1.10.0 (2018-02-08) ******************* Features: * Add support for marshmallow>=3.0.0b7 (:pr:`188`). Thanks :user:`lafrech`. Deprecations: * Support for aiohttp<2.0.0 is deprecated and will be removed in webargs 2.0.0. 1.9.0 (2018-02-03) ****************** Changes: * ``HTTPExceptions`` raised with `webargs.flaskparser.abort` will always have the ``data`` attribute, even if no additional keywords arguments are passed (:pr:`184`). Thanks :user:`lafrech`. Support: * Fix examples in examples/ directory. 1.8.1 (2017-07-17) ****************** Bug fixes: * Fix behavior of ``AIOHTTPParser.use_args`` when ``as_kwargs=True`` is passed with a ``Schema`` (:issue:`179`). Thanks :user:`Itayazolay`. 1.8.0 (2017-07-16) ****************** Features: * ``AIOHTTPParser`` supports class-based views, i.e. ``aiohttp.web.View`` (:issue:`177`). Thanks :user:`daniel98321`. 1.7.0 (2017-06-03) ****************** Features: * ``AIOHTTPParser.use_args`` and ``AIOHTTPParser.use_kwargs`` work with `async def` coroutines (:issue:`170`). Thanks :user:`zaro`. 1.6.3 (2017-05-18) ****************** Support: * Fix Flask error handling docs in "Framework support" section (:issue:`168`). Thanks :user:`nebularazer`. 1.6.2 (2017-05-16) ****************** Bug fixes: * Fix parsing multiple arguments in ``AIOHTTParser`` (:issue:`165`). Thanks :user:`ariddell` for reporting and thanks :user:`zaro` for reporting. 1.6.1 (2017-04-30) ****************** Bug fixes: * Fix form parsing in aiohttp>=2.0.0. Thanks :user:`DmitriyS` for the PR. 1.6.0 (2017-03-14) ****************** Bug fixes: * Fix compatibility with marshmallow 3.x. Other changes: * Drop support for Python 2.6 and 3.3. * Support marshmallow>=2.7.0. 1.5.3 (2017-02-04) ****************** Bug fixes: * Port fix from release 1.5.2 to `AsyncParser`. This fixes :issue:`146` for ``AIOHTTPParser``. * Handle invalid types passed to ``DelimitedList`` (:issue:`149`). Thanks :user:`psconnect-dev` for reporting. 1.5.2 (2017-01-08) ****************** Bug fixes: * Don't add ``marshmallow.missing`` to ``original_data`` when using ``marshmallow.validates_schema(pass_original=True)`` (:issue:`146`). Thanks :user:`lafrech` for reporting and for the fix. Other changes: * Test against Python 3.6. 1.5.1 (2016-11-27) ****************** Bug fixes: * Fix handling missing nested args when ``many=True`` (:issue:`120`, :issue:`145`). Thanks :user:`chavz` and :user:`Bangertm` for reporting. * Fix behavior of ``load_from`` in ``AIOHTTPParser``. 1.5.0 (2016-11-22) ****************** Features: * The ``use_args`` and ``use_kwargs`` decorators add a reference to the undecorated function via the ``__wrapped__`` attribute. This is useful for unit-testing purposes (:issue:`144`). Thanks :user:`EFF` for the PR. Bug fixes: * If ``load_from`` is specified on a field, first check the field name before checking ``load_from`` (:issue:`118`). Thanks :user:`jasonab` for reporting. 1.4.0 (2016-09-29) ****************** Bug fixes: * Prevent error when rendering validation errors to JSON in Flask (e.g. when using Flask-RESTful) (:issue:`122`). Thanks :user:`frol` for the catch and patch. NOTE: Though this is a bugfix, this is a potentially breaking change for code that needs to access the original ``ValidationError`` object. .. code-block:: python # Before @app.errorhandler(422) def handle_validation_error(err): return jsonify({"errors": err.messages}), 422 # After @app.errorhandler(422) def handle_validation_error(err): # The marshmallow.ValidationError is available on err.exc return jsonify({"errors": err.exc.messages}), 422 1.3.4 (2016-06-11) ****************** Bug fixes: * Fix bug in parsing form in Falcon>=1.0. 1.3.3 (2016-05-29) ****************** Bug fixes: * Fix behavior for nullable List fields (:issue:`107`). Thanks :user:`shaicantor` for reporting. 1.3.2 (2016-04-14) ****************** Bug fixes: * Fix passing a schema factory to ``use_kwargs`` (:issue:`103`). Thanks :user:`ksesong` for reporting. 1.3.1 (2016-04-13) ****************** Bug fixes: * Fix memory leak when calling ``parser.parse`` with a ``dict`` in a view (:issue:`101`). Thanks :user:`frankslaughter` for reporting. * aiohttpparser: Fix bug in handling bulk-type arguments. Support: * Massive refactor of tests (:issue:`98`). * Docs: Fix incorrect use_args example in Tornado section (:issue:`100`). Thanks :user:`frankslaughter` for reporting. * Docs: Add "Mixing Locations" section (:issue:`90`). Thanks :user:`tuukkamustonen`. 1.3.0 (2016-04-05) ****************** Features: * Add bulk-type arguments support for JSON parsing by passing ``many=True`` to a ``Schema`` (:issue:`81`). Thanks :user:`frol`. Bug fixes: * Fix JSON parsing in Flask<=0.9.0. Thanks :user:`brettdh` for the PR. * Fix behavior of ``status_code`` argument to ``ValidationError`` (:issue:`85`). This requires **marshmallow>=2.7.0**. Thanks :user:`ParthGandhi` for reporting. Support: * Docs: Add "Custom Fields" section with example of using a ``Function`` field (:issue:`94`). Thanks :user:`brettdh` for the suggestion. 1.2.0 (2016-01-04) ****************** Features: * Add ``view_args`` request location to ``FlaskParser`` (:issue:`82`). Thanks :user:`oreza` for the suggestion. Bug fixes: * Use the value of ``load_from`` as the key for error messages when it is provided (:issue:`83`). Thanks :user:`immerrr` for the catch and patch. 1.1.1 (2015-11-14) ****************** Bug fixes: * aiohttpparser: Fix bug that raised a ``JSONDecodeError`` raised when parsing non-JSON requests using default ``locations`` (:issue:`80`). Thanks :user:`leonidumanskiy` for reporting. * Fix parsing JSON requests that have a vendor media type, e.g. ``application/vnd.api+json``. 1.1.0 (2015-11-08) ****************** Features: * ``Parser.parse``, ``Parser.use_args`` and ``Parser.use_kwargs`` can take a Schema factory as the first argument (:issue:`73`). Thanks :user:`DamianHeard` for the suggestion and the PR. Support: * Docs: Add "Custom Parsers" section with example of parsing nested querystring arguments (:issue:`74`). Thanks :user:`dwieeb`. * Docs: Add "Advanced Usage" page. 1.0.0 (2015-10-19) ****************** Features: * Add ``AIOHTTPParser`` (:issue:`71`). * Add ``webargs.async`` module with ``AsyncParser``. Bug fixes: * If an empty list is passed to a List argument, it will be parsed as an empty list rather than being excluded from the parsed arguments dict (:issue:`70`). Thanks :user:`mTatcher` for catching this. Other changes: * *Backwards-incompatible*: When decorating resource methods with ``FalconParser.use_args``, the parsed arguments dictionary will be positioned **after** the request and response arguments. * *Backwards-incompatible*: When decorating views with ``DjangoParser.use_args``, the parsed arguments dictionary will be positioned **after** the request argument. * *Backwards-incompatible*: ``Parser.get_request_from_view_args`` gets passed a view function as its first argument. * *Backwards-incompatible*: Remove logging from default error handlers. 0.18.0 (2015-10-04) ******************* Features: * Add ``FalconParser`` (:issue:`63`). * Add ``fields.DelimitedList`` (:issue:`66`). Thanks :user:`jmcarp`. * ``TornadoParser`` will parse json with ``simplejson`` if it is installed. * ``BottleParser`` caches parsed json per-request for improved performance. No breaking changes. Yay! 0.17.0 (2015-09-29) ******************* Features: * ``TornadoParser`` returns unicode strings rather than bytestrings (:issue:`41`). Thanks :user:`thomasboyt` for the suggestion. * Add ``Parser.get_default_request`` and ``Parser.get_request_from_view_args`` hooks to simplify ``Parser`` implementations. * *Backwards-compatible*: ``webargs.core.get_value`` takes a ``Field`` as its last argument. Note: this is technically a breaking change, but this won't affect most users since ``get_value`` is only used internally by ``Parser`` classes. Support: * Add ``examples/annotations_example.py`` (demonstrates using Python 3 function annotations to define request arguments). * Fix examples. Thanks :user:`hyunchel` for catching an error in the Flask error handling docs. Bug fixes: * Correctly pass ``validate`` and ``force_all`` params to ``PyramidParser.use_args``. 0.16.0 (2015-09-27) ******************* The major change in this release is that webargs now depends on `marshmallow `_ for defining arguments and validation. Your code will need to be updated to use ``Fields`` rather than ``Args``. .. code-block:: python # Old API from webargs import Arg args = { "name": Arg(str, required=True), "password": Arg(str, validate=lambda p: len(p) >= 6), "display_per_page": Arg(int, default=10), "nickname": Arg(multiple=True), "Content-Type": Arg(dest="content_type", location="headers"), "location": Arg({"city": Arg(str), "state": Arg(str)}), "meta": Arg(dict), } # New API from webargs import fields args = { "name": fields.Str(required=True), "password": fields.Str(validate=lambda p: len(p) >= 6), "display_per_page": fields.Int(missing=10), "nickname": fields.List(fields.Str()), "content_type": fields.Str(load_from="Content-Type"), "location": fields.Nested({"city": fields.Str(), "state": fields.Str()}), "meta": fields.Dict(), } Features: * Error messages for all arguments are "bundled" (:issue:`58`). Changes: * *Backwards-incompatible*: Replace ``Args`` with marshmallow fields (:issue:`61`). * *Backwards-incompatible*: When using ``use_kwargs``, missing arguments will have the special value ``missing`` rather than ``None``. * ``TornadoParser`` raises a custom ``HTTPError`` with a ``messages`` attribute when validation fails. Bug fixes: * Fix required validation of nested arguments (:issue:`39`, :issue:`51`). These are fixed by virtue of using marshmallow's ``Nested`` field. Thanks :user:`ewang` and :user:`chavz` for reporting. Support: * Updated docs. * Add ``examples/schema_example.py``. * Tested against Python 3.5. 0.15.0 (2015-08-22) ******************* Changes: * If a parsed argument is ``None``, the type conversion function is not called :issue:`54`. Thanks :user:`marcellarius`. Bug fixes: * Fix parsing nested ``Args`` when the argument is missing from the input (:issue:`52`). Thanks :user:`stas`. 0.14.0 (2015-06-28) ******************* Features: * Add parsing of ``matchdict`` to ``PyramidParser``. Thanks :user:`hartror`. Bug fixes: * Fix ``PyramidParser's`` ``use_kwargs`` method (:issue:`42`). Thanks :user:`hartror` for the catch and patch. * Correctly use locations passed to Parser's constructor when using ``use_args`` (:issue:`44`). Thanks :user:`jacebrowning` for the catch and patch. * Fix behavior of ``default`` and ``dest`` argument on nested ``Args`` (:issue:`40` and :issue:`46`). Thanks :user:`stas`. Changes: * A 422 response is returned to the client when a ``ValidationError`` is raised by a parser (:issue:`38`). 0.13.0 (2015-04-05) ******************* Features: * Support for webapp2 via the `webargs.webapp2parser` module. Thanks :user:`Trii`. * Store argument name on ``RequiredArgMissingError``. Thanks :user:`stas`. * Allow error messages for required validation to be overriden. Thanks again :user:`stas`. Removals: * Remove ``source`` parameter from ``Arg``. 0.12.0 (2015-03-22) ******************* Features: * Store argument name on ``ValidationError`` (:issue:`32`). Thanks :user:`alexmic` for the suggestion. Thanks :user:`stas` for the patch. * Allow nesting of dict subtypes. 0.11.0 (2015-03-01) ******************* Changes: * Add ``dest`` parameter to ``Arg`` constructor which determines the key to be added to the parsed arguments dictionary (:issue:`32`). * *Backwards-incompatible*: Rename ``targets`` parameter to ``locations`` in ``Parser`` constructor, ``Parser#parse_arg``, ``Parser#parse``, ``Parser#use_args``, and ``Parser#use_kwargs``. * *Backwards-incompatible*: Rename ``Parser#target_handler`` to ``Parser#location_handler``. Deprecation: * The ``source`` parameter is deprecated in favor of the ``dest`` parameter. Bug fixes: * Fix ``validate`` parameter of ``DjangoParser#use_args``. 0.10.0 (2014-12-23) ******************* * When parsing a nested ``Arg``, filter out extra arguments that are not part of the ``Arg's`` nested ``dict`` (:issue:`28`). Thanks Derrick Gilland for the suggestion. * Fix bug in parsing ``Args`` with both type coercion and ``multiple=True`` (:issue:`30`). Thanks Steven Manuatu for reporting. * Raise ``RequiredArgMissingError`` when a required argument is missing on a request. 0.9.1 (2014-12-11) ****************** * Fix behavior of ``multiple=True`` when nesting Args (:issue:`29`). Thanks Derrick Gilland for reporting. 0.9.0 (2014-12-08) ****************** * Pyramid support thanks to @philtay. * User-friendly error messages when ``Arg`` type conversion/validation fails. Thanks Andriy Yurchuk. * Allow ``use`` argument to be a list of functions. * Allow ``Args`` to be nested within each other, e.g. for nested dict validation. Thanks @saritasa for the suggestion. * *Backwards-incompatible*: Parser will only pass ``ValidationErrors`` to its error handler function, rather than catching all generic Exceptions. * *Backwards-incompatible*: Rename ``Parser.TARGET_MAP`` to ``Parser.__target_map__``. * Add a short-lived cache to the ``Parser`` class that can be used to store processed request data for reuse. * Docs: Add example usage with Flask-RESTful. 0.8.1 (2014-10-28) ****************** * Fix bug in ``TornadoParser`` that raised an error when request body is not a string (e.g when it is a ``Future``). Thanks Josh Carp. 0.8.0 (2014-10-26) ****************** * Fix ``Parser.use_kwargs`` behavior when an ``Arg`` is allowed missing. The ``allow_missing`` attribute is ignored when ``use_kwargs`` is called. * ``default`` may be a callable. * Allow ``ValidationError`` to specify a HTTP status code for the error response. * Improved error logging. * Add ``'query'`` as a valid target name. * Allow a list of validators to be passed to an ``Arg`` or ``Parser.parse``. * A more useful ``__repr__`` for ``Arg``. * Add examples and updated docs. 0.7.0 (2014-10-18) ****************** * Add ``source`` parameter to ``Arg`` constructor. Allows renaming of keys in the parsed arguments dictionary. Thanks Josh Carp. * ``FlaskParser's`` ``handle_error`` method attaches the string representation of validation errors on ``err.data['message']``. The raised exception is stored on ``err.data['exc']``. * Additional keyword arguments passed to ``Arg`` are stored as metadata. 0.6.2 (2014-10-05) ****************** * Fix bug in ``TornadoParser's`` ``handle_error`` method. Thanks Josh Carp. * Add ``error`` parameter to ``Parser`` constructor that allows a custom error message to be used if schema-level validation fails. * Fix bug that raised a ``UnicodeEncodeError`` on Python 2 when an Arg's validator function received non-ASCII input. 0.6.1 (2014-09-28) ****************** * Fix regression with parsing an ``Arg`` with both ``default`` and ``target`` set (see issue #11). 0.6.0 (2014-09-23) ****************** * Add ``validate`` parameter to ``Parser.parse`` and ``Parser.use_args``. Allows validation of the full parsed output. * If ``allow_missing`` is ``True`` on an ``Arg`` for which ``None`` is explicitly passed, the value will still be present in the parsed arguments dictionary. * *Backwards-incompatible*: ``Parser's`` ``parse_*`` methods return ``webargs.core.Missing`` if the value cannot be found on the request. NOTE: ``webargs.core.Missing`` will *not* show up in the final output of ``Parser.parse``. * Fix bug with parsing empty request bodies with ``TornadoParser``. 0.5.1 (2014-08-30) ****************** * Fix behavior of ``Arg's`` ``allow_missing`` parameter when ``multiple=True``. * Fix bug in tornadoparser that caused parsing JSON arguments to fail. 0.5.0 (2014-07-27) ****************** * Fix JSON parsing in Flask parser when Content-Type header contains more than just `application/json`. Thanks Samir Uppaluru for reporting. * *Backwards-incompatible*: The ``use`` parameter to ``Arg`` is called before type conversion occurs. Thanks Eric Wang for the suggestion. * Tested on Tornado>=4.0. 0.4.0 (2014-05-04) ****************** * Custom target handlers can be defined using the ``Parser.target_handler`` decorator. * Error handler can be specified using the ``Parser.error_handler`` decorator. * ``Args`` can define their request target by passing in a ``target`` argument. * *Backwards-incompatible*: ``DEFAULT_TARGETS`` is now a class member of ``Parser``. This allows subclasses to override it. 0.3.4 (2014-04-27) ****************** * Fix bug that caused ``use_args`` to fail on class-based views in Flask. * Add ``allow_missing`` parameter to ``Arg``. 0.3.3 (2014-03-20) ****************** * Awesome contributions from the open-source community! * Add ``use_kwargs`` decorator. Thanks @venuatu. * Tornado support thanks to @jvrsantacruz. * Tested on Python 3.4. 0.3.2 (2014-03-04) ****************** * Fix bug with parsing JSON in Flask and Bottle. 0.3.1 (2014-03-03) ****************** * Remove print statements in core.py. Oops. 0.3.0 (2014-03-02) ****************** * Add support for repeated parameters (#1). * *Backwards-incompatible*: All `parse_*` methods take `arg` as their fourth argument. * Add ``error_handler`` param to ``Parser``. 0.2.0 (2014-02-26) ****************** * Bottle support. * Add ``targets`` param to ``Parser``. Allows setting default targets. * Add ``files`` target. 0.1.0 (2014-02-16) ****************** * First release. * Parses JSON, querystring, forms, headers, and cookies. * Support for Flask and Django. python-webargs_8.0.1.orig/CODE_OF_CONDUCT.md0000644000000000000000000000013413433772441015245 0ustar00For the code of conduct, see https://marshmallow.readthedocs.io/en/dev/code_of_conduct.html python-webargs_8.0.1.orig/CONTRIBUTING.rst0000644000000000000000000001064613775063555015130 0ustar00Contributing Guidelines ======================= Security Contact Information ---------------------------- To report a security vulnerability, please use the `Tidelift security contact `_. Tidelift will coordinate the fix and disclosure. Questions, Feature Requests, Bug Reports, and Feedback… ------------------------------------------------------- …should all be reported on the `GitHub Issue Tracker`_ . .. _`GitHub Issue Tracker`: https://github.com/marshmallow-code/webargs/issues?state=open Contributing Code ----------------- Integration with a Another Web Framework… +++++++++++++++++++++++++++++++++++++++++ …should be released as a separate package. **Pull requests adding support for another framework will not be accepted**. In order to keep webargs small and easy to maintain, we are not currently adding support for more frameworks. Instead, release your framework integration as a separate package and add it to the `Ecosystem `_ page in the `GitHub wiki `_ . Setting Up for Local Development ++++++++++++++++++++++++++++++++ 1. Fork webargs_ on GitHub. :: $ git clone https://github.com/marshmallow-code/webargs.git $ cd webargs 2. Install development requirements. **It is highly recommended that you use a virtualenv.** Use the following command to install an editable version of webargs 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:: webargs uses `black `_ for code formatting, which is only compatible with Python>=3.6. Therefore, the pre-commit hooks require a minimum Python version of 3.6. Git Branch Structure ++++++++++++++++++++ Webargs 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 `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 .. _webargs: https://github.com/marshmallow-code/webargs python-webargs_8.0.1.orig/LICENSE0000644000000000000000000000206213775063555013465 0ustar00Copyright 2014-2020 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-webargs_8.0.1.orig/MANIFEST.in0000644000000000000000000000007213775063555014215 0ustar00graft tests include LICENSE include *.rst include tox.ini python-webargs_8.0.1.orig/NOTICE0000644000000000000000000000606513326636200013354 0ustar00webargs includes some code from third-party libraries. Flask-Restful License ===================== Copyright (c) 2013, Twilio, Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - Neither the name of the Twilio, Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Werkzeug License ================ Copyright (c) 2014 by the Werkzeug Team, see AUTHORS for more details. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * The names of the contributors may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. python-webargs_8.0.1.orig/README.rst0000644000000000000000000000727214163275625014152 0ustar00******* webargs ******* .. image:: https://badgen.net/pypi/v/webargs :target: https://pypi.org/project/webargs/ :alt: PyPI version .. image:: https://dev.azure.com/sloria/sloria/_apis/build/status/marshmallow-code.webargs?branchName=dev :target: https://dev.azure.com/sloria/sloria/_build/latest?definitionId=6&branchName=dev :alt: Build status .. image:: https://readthedocs.org/projects/webargs/badge/ :target: https://webargs.readthedocs.io/ :alt: Documentation .. image:: https://badgen.net/badge/marshmallow/3 :target: https://marshmallow.readthedocs.io/en/latest/upgrading.html :alt: marshmallow 3 compatible .. image:: https://badgen.net/badge/code%20style/black/000 :target: https://github.com/ambv/black :alt: code style: black Homepage: https://webargs.readthedocs.io/ webargs is a Python library for parsing and validating HTTP request objects, with built-in support for popular web frameworks, including Flask, Django, Bottle, Tornado, Pyramid, Falcon, and aiohttp. .. code-block:: python from flask import Flask from webargs import fields from webargs.flaskparser import use_args app = Flask(__name__) @app.route("/") @use_args({"name": fields.Str(required=True)}, location="query") def index(args): return "Hello " + args["name"] if __name__ == "__main__": app.run() # curl http://localhost:5000/\?name\='World' # Hello World Install ======= :: pip install -U webargs webargs supports Python >= 3.6. Documentation ============= Full documentation is available at https://webargs.readthedocs.io/. Support webargs =============== webargs is maintained by a group of `volunteers `_. If you'd like to support the future of the project, please consider contributing to our Open Collective: .. image:: https://opencollective.com/marshmallow/donate/button.png :target: https://opencollective.com/marshmallow :width: 200 :alt: Donate to our collective Professional Support ==================== Professionally-supported webargs is available through the `Tidelift Subscription `_. Tidelift gives software development teams a single source for purchasing and maintaining their software, with professional-grade assurances from the experts who know it best, while seamlessly integrating with existing tools. [`Get professional support`_] .. _`Get professional support`: https://tidelift.com/subscription/pkg/pypi-webargs?utm_source=pypi-webargs&utm_medium=referral&utm_campaign=readme .. image:: https://user-images.githubusercontent.com/2379650/45126032-50b69880-b13f-11e8-9c2c-abd16c433495.png :target: https://tidelift.com/subscription/pkg/pypi-webargs?utm_source=pypi-webargs&utm_medium=referral&utm_campaign=readme :alt: Get supported marshmallow with Tidelift Security Contact Information ============================ To report a security vulnerability, please use the `Tidelift security contact `_. Tidelift will coordinate the fix and disclosure. Project Links ============= - Docs: https://webargs.readthedocs.io/ - Changelog: https://webargs.readthedocs.io/en/latest/changelog.html - Contributing Guidelines: https://webargs.readthedocs.io/en/latest/contributing.html - PyPI: https://pypi.python.org/pypi/webargs - Issues: https://github.com/marshmallow-code/webargs/issues - Ecosystem / related packages: https://github.com/marshmallow-code/webargs/wiki/Ecosystem License ======= MIT licensed. See the `LICENSE `_ file for more details. python-webargs_8.0.1.orig/azure-pipelines.yml0000644000000000000000000000150113775063555016314 0ustar00trigger: branches: include: [dev, test-me-*] tags: include: ['*'] # Run builds nightly to catch incompatibilities with new marshmallow releases schedules: - cron: "0 0 * * *" displayName: Daily midnight build branches: include: - dev always: "true" resources: repositories: - repository: sloria type: github endpoint: github name: sloria/azure-pipeline-templates ref: refs/heads/sloria jobs: - template: job--python-tox.yml@sloria parameters: toxenvs: - lint - mypy - py36 - py36-mindeps - py37 - py38 - py39 - py39-marshmallowdev - docs os: linux # Build wheels - template: job--pypi-release.yml@sloria parameters: python: "3.9" distributions: "sdist bdist_wheel" dependsOn: - tox_linux python-webargs_8.0.1.orig/docs/0000755000000000000000000000000013326636200013371 5ustar00python-webargs_8.0.1.orig/examples/0000755000000000000000000000000013326636200014257 5ustar00python-webargs_8.0.1.orig/pyproject.toml0000644000000000000000000000012013775063555015365 0ustar00[tool.black] line-length = 88 target-version = ['py35', 'py36', 'py37', 'py38'] python-webargs_8.0.1.orig/setup.cfg0000644000000000000000000000026514163275625014277 0ustar00[metadata] license_files = LICENSE [flake8] ignore = E203, E266, E501, W503 max-line-length = 80 max-complexity = 18 select = B,C,E,F,W,T4,B9 [mypy] ignore_missing_imports = true python-webargs_8.0.1.orig/setup.py0000644000000000000000000000647614163275625014202 0ustar00import re from setuptools import setup, find_packages FRAMEWORKS = [ "Flask>=0.12.5", "Django>=2.2.0", "bottle>=0.12.13", "tornado>=4.5.2", "pyramid>=1.9.1", "falcon>=2.0.0", "aiohttp>=3.0.8", ] EXTRAS_REQUIRE = { "frameworks": FRAMEWORKS, "tests": [ "pytest", "webtest==2.0.35", "webtest-aiohttp==2.0.0", "pytest-aiohttp>=0.3.0", ] + FRAMEWORKS, "lint": [ "mypy==0.910", "flake8==3.9.2", "flake8-bugbear==21.4.3", "pre-commit~=2.4", ], "docs": [ "Sphinx==4.1.2", "sphinx-issues==1.2.0", "furo==2021.8.11b42", ] + FRAMEWORKS, } EXTRAS_REQUIRE["dev"] = EXTRAS_REQUIRE["tests"] + EXTRAS_REQUIRE["lint"] + ["tox"] def find_version(fname): """Attempts to find the version number in the file names fname. Raises RuntimeError if not found. """ version = "" with open(fname) as fp: reg = re.compile(r'__version__ = [\'"]([^\'"]*)[\'"]') for line in fp: m = reg.match(line) if m: version = m.group(1) break if not version: raise RuntimeError("Cannot find version information") return version def read(fname): with open(fname) as fp: content = fp.read() return content setup( name="webargs", version=find_version("src/webargs/__init__.py"), description=( "Declarative parsing and validation of HTTP request objects, " "with built-in support for popular web frameworks, including " "Flask, Django, Bottle, Tornado, Pyramid, Falcon, and aiohttp." ), long_description=read("README.rst"), author="Steven Loria", author_email="sloria1@gmail.com", url="https://github.com/marshmallow-code/webargs", packages=find_packages("src"), package_dir={"": "src"}, package_data={"webargs": ["py.typed"]}, install_requires=["marshmallow>=3.0.0"], extras_require=EXTRAS_REQUIRE, license="MIT", zip_safe=False, keywords=( "webargs", "http", "flask", "django", "bottle", "tornado", "aiohttp", "request", "arguments", "validation", "parameters", "rest", "api", "marshmallow", ), python_requires=">=3.6", classifiers=[ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Natural Language :: English", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3 :: Only", "Topic :: Internet :: WWW/HTTP :: Dynamic Content", "Topic :: Internet :: WWW/HTTP :: WSGI :: Application", ], test_suite="tests", project_urls={ "Changelog": "https://webargs.readthedocs.io/en/latest/changelog.html", "Issues": "https://github.com/marshmallow-code/webargs/issues", "Funding": "https://opencollective.com/marshmallow", "Tidelift": "https://tidelift.com/subscription/pkg/pypi-webargs?utm_source=pypi-marshmallow&utm_medium=pypi", # noqa }, ) python-webargs_8.0.1.orig/src/0000755000000000000000000000000013654272604013240 5ustar00python-webargs_8.0.1.orig/tests/0000755000000000000000000000000013326636200013603 5ustar00python-webargs_8.0.1.orig/tox.ini0000644000000000000000000000235714163275625013775 0ustar00[tox] envlist= lint py{36,37,38,39} py36-mindeps py39-marshmallowdev docs [testenv] extras = tests deps = !marshmallowdev: marshmallow>=3.0.0,<4.0.0 marshmallowdev: https://github.com/marshmallow-code/marshmallow/archive/dev.tar.gz mindeps: Flask==0.12.5 mindeps: Django==2.2.0 mindeps: bottle==0.12.13 mindeps: tornado==4.5.2 mindeps: pyramid==1.9.1 mindeps: falcon==2.0.0 mindeps: aiohttp==3.0.8 commands = pytest {posargs} [testenv:lint] deps = pre-commit~=2.4 skip_install = true commands = pre-commit run --all-files # a separate `mypy` target which runs `mypy` in an environment with # `webargs` and `marshmallow` both installed is a valuable safeguard against # issues in which `mypy` running on every file standalone won't catch things [testenv:mypy] deps = mypy extras = frameworks commands = mypy src/ [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/webargs --delay 2 [testenv:watch-readme] deps = restview skip_install = true commands = restview README.rst python-webargs_8.0.1.orig/.github/FUNDING.yml0000644000000000000000000000007013654272604015623 0ustar00open_collective: "marshmallow" tidelift: "pypi/webargs" python-webargs_8.0.1.orig/.github/dependabot.yml0000644000000000000000000000017514163275625016646 0ustar00version: 2 updates: - package-ecosystem: pip directory: "/" schedule: interval: daily open-pull-requests-limit: 10 python-webargs_8.0.1.orig/docs/Makefile0000644000000000000000000001517113326636200015036 0ustar00# 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-webargs_8.0.1.orig/docs/_static/0000755000000000000000000000000014163275625015031 5ustar00python-webargs_8.0.1.orig/docs/_templates/0000755000000000000000000000000013421337646015536 5ustar00python-webargs_8.0.1.orig/docs/advanced.rst0000644000000000000000000005154714163275625015716 0ustar00Advanced Usage ============== This section includes guides for advanced usage patterns. Custom Location Handlers ------------------------ To add your own custom location handler, write a function that receives a request, and a :class:`Schema `, then decorate that function with :func:`Parser.location_loader `. .. code-block:: python from webargs import fields from webargs.flaskparser import parser @parser.location_loader("data") def load_data(request, schema): return request.data # Now 'data' can be specified as a location @parser.use_args({"per_page": fields.Int()}, location="data") def posts(args): return "displaying {} posts".format(args["per_page"]) .. NOTE:: The schema is passed so that it can be used to wrap multidict types and unpack List fields correctly. If you are writing a loader for a multidict type, consider looking at :class:`MultiDictProxy ` for an example of how to do this. "meta" Locations ~~~~~~~~~~~~~~~~ You can define your own locations which mix data from several existing locations. The `json_or_form` location does this -- first trying to load data as JSON and then falling back to a form body -- and its implementation is quite simple: .. code-block:: python def load_json_or_form(self, req, schema): """Load data from a request, accepting either JSON or form-encoded data. The data will first be loaded as JSON, and, if that fails, it will be loaded as a form post. """ data = self.load_json(req, schema) if data is not missing: return data return self.load_form(req, schema) You can imagine your own locations with custom behaviors like this. For example, to mix query parameters and form body data, you might write the following: .. code-block:: python from webargs import fields from webargs.multidictproxy import MultiDictProxy from webargs.flaskparser import parser @parser.location_loader("query_and_form") def load_data(request, schema): # relies on the Flask (werkzeug) MultiDict type's implementation of # these methods, but when you're extending webargs, you may know things # about your framework of choice newdata = request.args.copy() newdata.update(request.form) return MultiDictProxy(newdata, schema) # Now 'query_and_form' means you can send these values in either location, # and they will be *mixed* together into a new dict to pass to your schema @parser.use_args({"favorite_food": fields.String()}, location="query_and_form") def set_favorite_food(args): ... # do stuff return "your favorite food is now set to {}".format(args["favorite_food"]) marshmallow Integration ----------------------- When you need more flexibility in defining input schemas, you can pass a marshmallow `Schema ` instead of a dictionary to `Parser.parse `, `Parser.use_args `, and `Parser.use_kwargs `. .. code-block:: python from marshmallow import Schema, fields from webargs.flaskparser import use_args class UserSchema(Schema): id = fields.Int(dump_only=True) # read-only (won't be parsed by webargs) username = fields.Str(required=True) password = fields.Str(load_only=True) # write-only first_name = fields.Str(missing="") last_name = fields.Str(missing="") date_registered = fields.DateTime(dump_only=True) @use_args(UserSchema()) def profile_view(args): username = args["username"] # ... @use_kwargs(UserSchema()) def profile_update(username, password, first_name, last_name): update_profile(username, password, first_name, last_name) # ... # You can add additional parameters @use_kwargs({"posts_per_page": fields.Int(missing=10)}, location="query") @use_args(UserSchema()) def profile_posts(args, posts_per_page): username = args["username"] # ... .. _advanced_setting_unknown: Setting `unknown` ----------------- webargs supports several ways of setting and passing the `unknown` parameter for `handling unknown fields `_. You can pass `unknown=...` as a parameter to any of `Parser.parse `, `Parser.use_args `, and `Parser.use_kwargs `. .. note:: The `unknown` value is passed to the schema's `load()` call. It therefore only applies to the top layer when nesting is used. To control `unknown` at multiple layers of a nested schema, you must use other mechanisms, like the `unknown` argument to `fields.Nested`. Default `unknown` ~~~~~~~~~~~~~~~~~ By default, webargs will pass `unknown=marshmallow.EXCLUDE` except when the location is `json`, `form`, `json_or_form`, or `path`. In those cases, it uses `unknown=marshmallow.RAISE` instead. You can change these defaults by overriding `DEFAULT_UNKNOWN_BY_LOCATION`. This is a mapping of locations to values to pass. For example, .. code-block:: python from flask import Flask from marshmallow import EXCLUDE, fields from webargs.flaskparser import FlaskParser app = Flask(__name__) class Parser(FlaskParser): DEFAULT_UNKNOWN_BY_LOCATION = {"query": EXCLUDE} parser = Parser() # location is "query", which is listed in DEFAULT_UNKNOWN_BY_LOCATION, # so EXCLUDE will be used @app.route("/", methods=["GET"]) @parser.use_args({"foo": fields.Int()}, location="query") def get(args): return f"foo x 2 = {args['foo'] * 2}" # location is "json", which is not in DEFAULT_UNKNOWN_BY_LOCATION, # so no value will be passed for `unknown` @app.route("/", methods=["POST"]) @parser.use_args({"foo": fields.Int(), "bar": fields.Int()}, location="json") def post(args): return f"foo x bar = {args['foo'] * args['bar']}" You can also define a default at parser instantiation, which will take precedence over these defaults, as in .. code-block:: python from marshmallow import INCLUDE parser = Parser(unknown=INCLUDE) # because `unknown` is set on the parser, `DEFAULT_UNKNOWN_BY_LOCATION` has # effect and `INCLUDE` will always be used @app.route("/", methods=["POST"]) @parser.use_args({"foo": fields.Int(), "bar": fields.Int()}, location="json") def post(args): unexpected_args = [k for k in args.keys() if k not in ("foo", "bar")] return f"foo x bar = {args['foo'] * args['bar']}; unexpected args={unexpected_args}" Using Schema-Specfied `unknown` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ If you wish to use the value of `unknown` specified by a schema, simply pass ``unknown=None``. This will disable webargs' automatic passing of values for ``unknown``. For example, .. code-block:: python from flask import Flask from marshmallow import Schema, fields, EXCLUDE, missing from webargs.flaskparser import use_args class RectangleSchema(Schema): length = fields.Float() width = fields.Float() class Meta: unknown = EXCLUDE app = Flask(__name__) # because unknown=None was passed, no value is passed during schema loading # as a result, the schema's behavior (EXCLUDE) is used @app.route("/", methods=["POST"]) @use_args(RectangleSchema(), location="json", unknown=None) def get(args): return f"area = {args['length'] * args['width']}" You can also set ``unknown=None`` when instantiating a parser to make this behavior the default for a parser. When to avoid `use_kwargs` -------------------------- Any `Schema ` passed to `use_kwargs ` MUST deserialize to a dictionary of data. If your schema has a `post_load ` method that returns a non-dictionary, you should use `use_args ` instead. .. code-block:: python from marshmallow import Schema, fields, post_load from webargs.flaskparser import use_args class Rectangle: def __init__(self, length, width): self.length = length self.width = width class RectangleSchema(Schema): length = fields.Float() width = fields.Float() @post_load def make_object(self, data, **kwargs): return Rectangle(**data) @use_args(RectangleSchema) def post(rect: Rectangle): return f"Area: {rect.length * rect.width}" Packages such as `marshmallow-sqlalchemy `_ and `marshmallow-dataclass `_ generate schemas that deserialize to non-dictionary objects. Therefore, `use_args ` should be used with those schemas. Schema Factories ---------------- If you need to parametrize a schema based on a given request, you can use a "Schema factory": a callable that receives the current `request` and returns a `marshmallow.Schema` instance. Consider the following use cases: - Filtering via a query parameter by passing ``only`` to the Schema. - Handle partial updates for PATCH requests using marshmallow's `partial loading `_ API. .. code-block:: python from flask import Flask from marshmallow import Schema, fields from webargs.flaskparser import use_args app = Flask(__name__) class UserSchema(Schema): id = fields.Int(dump_only=True) username = fields.Str(required=True) password = fields.Str(load_only=True) first_name = fields.Str(missing="") last_name = fields.Str(missing="") date_registered = fields.DateTime(dump_only=True) def make_user_schema(request): # Filter based on 'fields' query parameter fields = request.args.get("fields", None) only = fields.split(",") if fields else None # Respect partial updates for PATCH requests partial = request.method == "PATCH" # Add current request to the schema's context return UserSchema(only=only, partial=partial, context={"request": request}) # Pass the factory to .parse, .use_args, or .use_kwargs @app.route("/profile/", methods=["GET", "POST", "PATCH"]) @use_args(make_user_schema) def profile_view(args): username = args.get("username") # ... Reducing Boilerplate ~~~~~~~~~~~~~~~~~~~~ We can reduce boilerplate and improve [re]usability with a simple helper function: .. code-block:: python from webargs.flaskparser import use_args def use_args_with(schema_cls, schema_kwargs=None, **kwargs): schema_kwargs = schema_kwargs or {} def factory(request): # Filter based on 'fields' query parameter only = request.args.get("fields", None) # Respect partial updates for PATCH requests partial = request.method == "PATCH" return schema_cls( only=only, partial=partial, context={"request": request}, **schema_kwargs ) return use_args(factory, **kwargs) Now we can attach input schemas to our view functions like so: .. code-block:: python @use_args_with(UserSchema) def profile_view(args): # ... get_profile(**args) Custom Fields ------------- See the "Custom Fields" section of the marshmallow docs for a detailed guide on defining custom fields which you can pass to webargs parsers: https://marshmallow.readthedocs.io/en/latest/custom_fields.html. Using ``Method`` and ``Function`` Fields with webargs ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Using the :class:`Method ` and :class:`Function ` fields requires that you pass the ``deserialize`` parameter. .. code-block:: python @use_args({"cube": fields.Function(deserialize=lambda x: int(x) ** 3)}) def math_view(args): cube = args["cube"] # ... .. _custom-loaders: Custom Parsers -------------- To add your own parser, extend :class:`Parser ` and implement the `load_*` method(s) you need to override. For example, here is a custom Flask parser that handles nested query string arguments. .. code-block:: python import re from webargs import core from webargs.flaskparser import FlaskParser class NestedQueryFlaskParser(FlaskParser): """Parses nested query args This parser handles nested query args. It expects nested levels delimited by a period and then deserializes the query args into a nested dict. For example, the URL query params `?name.first=John&name.last=Boone` will yield the following dict: { 'name': { 'first': 'John', 'last': 'Boone', } } """ def load_querystring(self, req, schema): return _structure_dict(req.args) def _structure_dict(dict_): def structure_dict_pair(r, key, value): m = re.match(r"(\w+)\.(.*)", key) if m: if r.get(m.group(1)) is None: r[m.group(1)] = {} structure_dict_pair(r[m.group(1)], m.group(2), value) else: r[key] = value r = {} for k, v in dict_.items(): structure_dict_pair(r, k, v) return r Parser pre_load --------------- Similar to ``@pre_load`` decorated hooks on marshmallow Schemas, :class:`Parser ` classes define a method, `pre_load ` which can be overridden to provide per-parser transformations of data. The only way to make use of `pre_load ` is to subclass a :class:`Parser ` and provide an implementation. `pre_load ` is given the data fetched from a location, the schema which will be used, the request object, and the location name which was requested. For example, to define a ``FlaskParser`` which strips whitespace from ``form`` and ``query`` data, one could write the following: .. code-block:: python from webargs.flaskparser import FlaskParser import typing def _strip_whitespace(value): if isinstance(value, str): value = value.strip() elif isinstance(value, typing.Mapping): return {k: _strip_whitespace(value[k]) for k in value} elif isinstance(value, (list, tuple)): return type(value)(map(_strip_whitespace, value)) return value class WhitspaceStrippingFlaskParser(FlaskParser): def pre_load(self, location_data, *, schema, req, location): if location in ("query", "form"): return _strip_whitespace(location_data) return location_data Note that `Parser.pre_load ` is run after location loading but before ``Schema.load`` is called. It can therefore be called on multiple types of mapping objects, including :class:`MultiDictProxy `, depending on what the location loader returns. Returning HTTP 400 Responses ---------------------------- If you'd prefer validation errors to return status code ``400`` instead of ``422``, you can override ``DEFAULT_VALIDATION_STATUS`` on a :class:`Parser `. Sublcass the parser for your framework to do so. For example, using Falcon: .. code-block:: python from webargs.falconparser import FalconParser class Parser(FalconParser): DEFAULT_VALIDATION_STATUS = 400 parser = Parser() use_args = parser.use_args use_kwargs = parser.use_kwargs Bulk-type Arguments ------------------- In order to parse a JSON array of objects, pass ``many=True`` to your input ``Schema`` . For example, you might implement JSON PATCH according to `RFC 6902 `_ like so: .. code-block:: python from webargs import fields from webargs.flaskparser import use_args from marshmallow import Schema, validate class PatchSchema(Schema): op = fields.Str( required=True, validate=validate.OneOf(["add", "remove", "replace", "move", "copy"]), ) path = fields.Str(required=True) value = fields.Str(required=True) @app.route("/profile/", methods=["patch"]) @use_args(PatchSchema(many=True)) def patch_blog(args): """Implements JSON Patch for the user profile Example JSON body: [ {"op": "replace", "path": "/email", "value": "mynewemail@test.org"} ] """ # ... Multi-Field Detection --------------------- If a ``List`` field is used to parse data from a location like query parameters -- where one or multiple values can be passed for a single parameter name -- then webargs will automatically treat that field as a list and parse multiple values if present. To implement this behavior, webargs will examine schemas for ``marshmallow.fields.List`` fields. ``List`` fields get unpacked to list values when data is loaded, and other fields do not. This also applies to fields which inherit from ``List``. .. note:: In webargs v8, ``Tuple`` will be treated this way as well, in addition to ``List``. What if you have a list which should be treated as a "multi-field" but which does not inherit from ``List``? webargs offers two solutions. You can add the custom attribute `is_multiple=True` to your field or you can add your class to your parser's list of `KNOWN_MULTI_FIELDS`. First, let's define a "multiplexing field" which takes a string or list of strings to serve as an example: .. code-block:: python # a custom field class which can accept values like List(String()) or String() class CustomMultiplexingField(fields.String): def _deserialize(self, value, attr, data, **kwargs): if isinstance(value, str): return super()._deserialize(value, attr, data, **kwargs) return [ self._deserialize(v, attr, data, **kwargs) for v in value if isinstance(v, str) ] def _serialize(self, value, attr, **kwargs): if isinstance(value, str): return super()._serialize(value, attr, **kwargs) return [self._serialize(v, attr, **kwargs) for v in value if isinstance(v, str)] If you control the definition of ``CustomMultiplexingField``, you can just add ``is_multiple=True`` to it: .. code-block:: python # option 1: define the field with is_multiple = True from webargs.flaskparser import parser class CustomMultiplexingField(fields.Field): is_multiple = True # <----- this marks this as a multi-field ... # as above If you don't control the definition of ``CustomMultiplexingField``, for example because it comes from a library, you can add it to the list of known multifields: .. code-block:: python # option 2: add the field to the parer's list of multi-fields class MyParser(FlaskParser): KNOWN_MULTI_FIELDS = list(FlaskParser.KNOWN_MULTI_FIELDS) + [ CustomMultiplexingField ] parser = MyParser() In either case, the end result is that you can use the multifield and it will be detected as a list when unpacking query string data: .. code-block:: python # gracefully handles # ...?foo=a # ...?foo=a&foo=b # and treats them as ["a"] and ["a", "b"] respectively @parser.use_args({"foo": CustomMultiplexingField()}, location="query") def show_foos(foo): ... Mixing Locations ---------------- Arguments for different locations can be specified by passing ``location`` to each `use_args ` call: .. code-block:: python # "json" is the default, used explicitly below @app.route("/stacked", methods=["POST"]) @use_args({"page": fields.Int(), "q": fields.Str()}, location="query") @use_args({"name": fields.Str()}, location="json") def viewfunc(query_parsed, json_parsed): page = query_parsed["page"] name = json_parsed["name"] # ... To reduce boilerplate, you could create shortcuts, like so: .. code-block:: python import functools query = functools.partial(use_args, location="query") body = functools.partial(use_args, location="json") @query({"page": fields.Int(), "q": fields.Int()}) @body({"name": fields.Str()}) def viewfunc(query_parsed, json_parsed): page = query_parsed["page"] name = json_parsed["name"] # ... Next Steps ---------- - See the :doc:`Framework Support ` page for framework-specific guides. - For example applications, check out the `examples `_ directory. python-webargs_8.0.1.orig/docs/api.rst0000644000000000000000000000207713775063555014721 0ustar00API === .. module:: webargs webargs.core ------------ .. automodule:: webargs.core :inherited-members: webargs.fields -------------- .. automodule:: webargs.fields :members: Nested, DelimitedList webargs.multidictproxy ---------------------- .. automodule:: webargs.multidictproxy :members: webargs.asyncparser ------------------- .. automodule:: webargs.asyncparser :inherited-members: webargs.flaskparser ------------------- .. automodule:: webargs.flaskparser :members: webargs.djangoparser -------------------- .. automodule:: webargs.djangoparser :members: webargs.bottleparser -------------------- .. automodule:: webargs.bottleparser :members: webargs.tornadoparser --------------------- .. automodule:: webargs.tornadoparser :members: webargs.pyramidparser --------------------- .. automodule:: webargs.pyramidparser :members: webargs.falconparser --------------------- .. automodule:: webargs.falconparser :members: webargs.aiohttpparser --------------------- .. automodule:: webargs.aiohttpparser :members: python-webargs_8.0.1.orig/docs/authors.rst0000644000000000000000000000003513326636200015606 0ustar00 .. include:: ../AUTHORS.rst python-webargs_8.0.1.orig/docs/changelog.rst0000644000000000000000000000003613326636200016051 0ustar00.. include:: ../CHANGELOG.rst python-webargs_8.0.1.orig/docs/conf.py0000755000000000000000000000410514163275625014705 0ustar00import sys import os import time import datetime as dt # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. sys.path.insert(0, os.path.abspath(os.path.join("..", "src"))) import webargs # noqa extensions = [ "sphinx.ext.autodoc", "sphinx.ext.viewcode", "sphinx.ext.intersphinx", "sphinx_issues", ] primary_domain = "py" default_role = "py:obj" github_user = "marshmallow-code" github_repo = "webargs" issues_github_path = f"{github_user}/{github_repo}" intersphinx_mapping = { "python": ("http://python.readthedocs.io/en/latest/", None), "marshmallow": ("http://marshmallow.readthedocs.io/en/latest/", None), } # Use SOURCE_DATE_EPOCH for reproducible build output # https://reproducible-builds.org/docs/source-date-epoch/ build_date = dt.datetime.utcfromtimestamp( int(os.environ.get("SOURCE_DATE_EPOCH", time.time())) ) # The master toctree document. master_doc = "index" language = "en" html_domain_indices = False source_suffix = ".rst" project = "webargs" copyright = f"2014-{build_date:%Y}, Steven Loria and contributors" version = release = webargs.__version__ templates_path = ["_templates"] exclude_patterns = ["_build"] # THEME html_theme = "furo" html_theme_options = { "light_css_variables": {"color-brand-primary": "#268bd2"}, "description": "Declarative parsing and validation of HTTP request objects.", } html_logo = "_static/logo.png" html_context = { "tidelift_url": ( "https://tidelift.com/subscription/pkg/pypi-webargs" "?utm_source=pypi-webargs&utm_medium=referral&utm_campaign=docs" ), "donate_url": "https://opencollective.com/marshmallow", } html_sidebars = { "*": [ "sidebar/scroll-start.html", "sidebar/brand.html", "sidebar/search.html", "sidebar/navigation.html", "donate.html", "sponsors.html", "sidebar/ethical-ads.html", "sidebar/scroll-end.html", ] } python-webargs_8.0.1.orig/docs/contributing.rst0000644000000000000000000000004113326636200016625 0ustar00.. include:: ../CONTRIBUTING.rst python-webargs_8.0.1.orig/docs/ecosystem.rst0000644000000000000000000000023313433772441016143 0ustar00Ecosystem ========= A list of webargs-related libraries can be found at the GitHub wiki here: https://github.com/marshmallow-code/webargs/wiki/Ecosystem python-webargs_8.0.1.orig/docs/framework_support.rst0000644000000000000000000003210113775063555017730 0ustar00.. _frameworks: Framework Support ================= This section includes notes for using webargs with specific web frameworks. Flask ----- Flask support is available via the :mod:`webargs.flaskparser` module. Decorator Usage +++++++++++++++ When using the :meth:`use_args ` decorator, the arguments dictionary will be *before* any URL variable parameters. .. code-block:: python from webargs import fields from webargs.flaskparser import use_args @app.route("/user/") @use_args({"per_page": fields.Int()}, location="query") def user_detail(args, uid): return ("The user page for user {uid}, showing {per_page} posts.").format( uid=uid, per_page=args["per_page"] ) Error Handling ++++++++++++++ Webargs uses Flask's ``abort`` function to raise an ``HTTPException`` when a validation error occurs. If you use the ``Flask.errorhandler`` method to handle errors, you can access validation messages from the ``messages`` attribute of the attached ``ValidationError``. Here is an example error handler that returns validation messages to the client as JSON. .. code-block:: python from flask import jsonify # Return validation errors as JSON @app.errorhandler(422) @app.errorhandler(400) def handle_error(err): headers = err.data.get("headers", None) messages = err.data.get("messages", ["Invalid request."]) if headers: return jsonify({"errors": messages}), err.code, headers else: return jsonify({"errors": messages}), err.code URL Matches +++++++++++ The `FlaskParser` supports parsing values from a request's ``view_args``. .. code-block:: python from webargs.flaskparser import use_args @app.route("/greeting//") @use_args({"name": fields.Str()}, location="view_args") def greeting(args, **kwargs): return "Hello {}".format(args["name"]) Django ------ Django support is available via the :mod:`webargs.djangoparser` module. Webargs can parse Django request arguments in both function-based and class-based views. Decorator Usage +++++++++++++++ When using the :meth:`use_args ` decorator, the arguments dictionary will positioned after the ``request`` argument. **Function-based Views** .. code-block:: python from django.http import HttpResponse from webargs import Arg from webargs.djangoparser import use_args account_args = { "username": fields.Str(required=True), "password": fields.Str(required=True), } @use_args(account_args, location="form") def login_user(request, args): if request.method == "POST": login(args["username"], args["password"]) return HttpResponse("Login page") **Class-based Views** .. code-block:: python from django.views.generic import View from django.shortcuts import render_to_response from webargs import fields from webargs.djangoparser import use_args blog_args = {"title": fields.Str(), "author": fields.Str()} class BlogPostView(View): @use_args(blog_args, location="query") def get(self, request, args): blog_post = Post.objects.get(title__iexact=args["title"], author=args["author"]) return render_to_response("post_template.html", {"post": blog_post}) Error Handling ++++++++++++++ The :class:`DjangoParser` does not override :meth:`handle_error `, so your Django views are responsible for catching any :exc:`ValidationErrors` raised by the parser and returning the appropriate `HTTPResponse`. .. code-block:: python from django.http import JsonResponse from webargs import fields, ValidationError, json argmap = {"name": fields.Str(required=True)} def index(request): try: args = parser.parse(argmap, request) except ValidationError as err: return JsonResponse(err.messages, status=422) except json.JSONDecodeError: return JsonResponse({"json": ["Invalid JSON body."]}, status=400) return JsonResponse({"message": "Hello {name}".format(name=name)}) Tornado ------- Tornado argument parsing is available via the :mod:`webargs.tornadoparser` module. The :class:`webargs.tornadoparser.TornadoParser` parses arguments from a :class:`tornado.httpserver.HTTPRequest` object. The :class:`TornadoParser ` can be used directly, or you can decorate handler methods with :meth:`use_args ` or :meth:`use_kwargs `. .. code-block:: python import tornado.ioloop import tornado.web from webargs import fields from webargs.tornadoparser import parser class HelloHandler(tornado.web.RequestHandler): hello_args = {"name": fields.Str()} def post(self, id): reqargs = parser.parse(self.hello_args, self.request) response = {"message": "Hello {}".format(reqargs["name"])} self.write(response) application = tornado.web.Application([(r"/hello/([0-9]+)", HelloHandler)], debug=True) if __name__ == "__main__": application.listen(8888) tornado.ioloop.IOLoop.instance().start() Decorator Usage +++++++++++++++ When using the :meth:`use_args ` decorator, the decorated method will have the dictionary of parsed arguments passed as a positional argument after ``self`` and any regex match groups from the URL spec. .. code-block:: python from webargs import fields from webargs.tornadoparser import use_args class HelloHandler(tornado.web.RequestHandler): @use_args({"name": fields.Str()}) def post(self, id, reqargs): response = {"message": "Hello {}".format(reqargs["name"])} self.write(response) application = tornado.web.Application([(r"/hello/([0-9]+)", HelloHandler)], debug=True) As with the other parser modules, :meth:`use_kwargs ` will add keyword arguments to the view callable. Error Handling ++++++++++++++ A `HTTPError ` will be raised in the event of a validation error. Your `RequestHandlers` are responsible for handling these errors. Here is how you could write the error messages to a JSON response. .. code-block:: python from tornado.web import RequestHandler class MyRequestHandler(RequestHandler): def write_error(self, status_code, **kwargs): """Write errors as JSON.""" self.set_header("Content-Type", "application/json") if "exc_info" in kwargs: etype, exc, traceback = kwargs["exc_info"] if hasattr(exc, "messages"): self.write({"errors": exc.messages}) if getattr(exc, "headers", None): for name, val in exc.headers.items(): self.set_header(name, val) self.finish() Pyramid ------- Pyramid support is available via the :mod:`webargs.pyramidparser` module. Decorator Usage +++++++++++++++ When using the :meth:`use_args ` decorator on a view callable, the arguments dictionary will be positioned after the `request` argument. .. code-block:: python from pyramid.response import Response from webargs import fields from webargs.pyramidparser import use_args @use_args({"uid": fields.Str(), "per_page": fields.Int()}, location="query") def user_detail(request, args): uid = args["uid"] return Response( "The user page for user {uid}, showing {per_page} posts.".format( uid=uid, per_page=args["per_page"] ) ) As with the other parser modules, :meth:`use_kwargs ` will add keyword arguments to the view callable. URL Matches +++++++++++ The `PyramidParser` supports parsing values from a request's matchdict. .. code-block:: python from pyramid.response import Response from webargs.pyramidparser import use_args @use_args({"mymatch": fields.Int()}, location="matchdict") def matched(request, args): return Response("The value for mymatch is {}".format(args["mymatch"])) Falcon ------ Falcon support is available via the :mod:`webargs.falconparser` module. Decorator Usage +++++++++++++++ When using the :meth:`use_args ` decorator on a resource method, the arguments dictionary will be positioned directly after the request and response arguments. .. code-block:: python import falcon from webargs import fields from webargs.falconparser import use_args class BlogResource: request_args = {"title": fields.Str(required=True)} @use_args(request_args) def on_post(self, req, resp, args, post_id): content = args["title"] # ... api = application = falcon.API() api.add_route("/blogs/{post_id}") As with the other parser modules, :meth:`use_kwargs ` will add keyword arguments to your resource methods. Hook Usage ++++++++++ You can easily implement hooks by using `parser.parse ` directly. .. code-block:: python import falcon from webargs import fields from webargs.falconparser import parser def add_args(argmap, **kwargs): def hook(req, resp, resource, params): parsed_args = parser.parse(argmap, req=req, **kwargs) req.context["args"] = parsed_args return hook @falcon.before(add_args({"page": fields.Int()}, location="query")) class AuthorResource: def on_get(self, req, resp): args = req.context["args"] page = args.get("page") # ... aiohttp ------- aiohttp support is available via the :mod:`webargs.aiohttpparser` module. The `parse ` method of `AIOHTTPParser ` is a `coroutine `. .. code-block:: python import asyncio from aiohttp import web from webargs import fields from webargs.aiohttpparser import parser handler_args = {"name": fields.Str(missing="World")} async def handler(request): args = await parser.parse(handler_args, request) return web.Response(body="Hello, {}".format(args["name"]).encode("utf-8")) Decorator Usage +++++++++++++++ When using the :meth:`use_args ` decorator on a handler, the parsed arguments dictionary will be the last positional argument. .. code-block:: python import asyncio from aiohttp import web from webargs import fields from webargs.aiohttpparser import use_args @use_args({"content": fields.Str(required=True)}) async def create_comment(request, args): content = args["content"] # ... app = web.Application() app.router.add_route("POST", "/comments/", create_comment) As with the other parser modules, :meth:`use_kwargs ` will add keyword arguments to your resource methods. Usage with coroutines +++++++++++++++++++++ The :meth:`use_args ` and :meth:`use_kwargs ` decorators will work with both `async def` coroutines and generator-based coroutines decorated with `asyncio.coroutine`. .. code-block:: python import asyncio from aiohttp import web from webargs import fields from webargs.aiohttpparser import use_kwargs hello_args = {"name": fields.Str(missing="World")} # The following are equivalent @asyncio.coroutine @use_kwargs(hello_args) def hello(request, name): return web.Response(body="Hello, {}".format(name).encode("utf-8")) @use_kwargs(hello_args) async def hello(request, name): return web.Response(body="Hello, {}".format(name).encode("utf-8")) URL Matches +++++++++++ The `AIOHTTPParser ` supports parsing values from a request's ``match_info``. .. code-block:: python from aiohttp import web from webargs.aiohttpparser import use_args @parser.use_args({"slug": fields.Str()}, location="match_info") def article_detail(request, args): return web.Response(body="Slug: {}".format(args["slug"]).encode("utf-8")) app = web.Application() app.router.add_route("GET", "/articles/{slug}", article_detail) Bottle ------ Bottle support is available via the :mod:`webargs.bottleparser` module. Decorator Usage +++++++++++++++ The preferred way to apply decorators to Bottle routes is using the ``apply`` argument. .. code-block:: python from bottle import route user_args = {"name": fields.Str(missing="Friend")} @route("/users/<_id:int>", method="GET", apply=use_args(user_args)) def users(args, _id): """A welcome page.""" return {"message": "Welcome, {}!".format(args["name"]), "_id": _id} python-webargs_8.0.1.orig/docs/index.rst0000644000000000000000000000604113775063555015252 0ustar00======= webargs ======= Release v\ |version|. (:doc:`Changelog `) webargs is a Python library for parsing and validating HTTP request objects, with built-in support for popular web frameworks, including Flask, Django, Bottle, Tornado, Pyramid, Falcon, and aiohttp. Upgrading from an older version? -------------------------------- See the :doc:`Upgrading to Newer Releases ` page for notes on getting your code up-to-date with the latest version. Usage and Simple Examples ------------------------- .. code-block:: python from flask import Flask from webargs import fields from webargs.flaskparser import use_args app = Flask(__name__) @app.route("/") @use_args({"name": fields.Str(required=True)}, location="query") def index(args): return "Hello " + args["name"] if __name__ == "__main__": app.run() # curl http://localhost:5000/\?name\='World' # Hello World By default Webargs will automatically parse JSON request bodies. But it also has support for: **Query Parameters** :: $ curl http://localhost:5000/\?name\='Freddie' Hello Freddie # pass location="query" to use_args **Form Data** :: $ curl -d 'name=Brian' http://localhost:5000/ Hello Brian # pass location="form" to use_args **JSON Data** :: $ curl -X POST -H "Content-Type: application/json" -d '{"name":"Roger"}' http://localhost:5000/ Hello Roger # pass location="json" (or omit location) to use_args and, optionally: - Headers - Cookies - Files - Paths Why Use It ---------- * **Simple, declarative syntax**. Define your arguments as a mapping rather than imperatively pulling values off of request objects. * **Code reusability**. If you have multiple views that have the same request parameters, you only need to define your parameters once. You can also reuse validation and pre-processing routines. * **Self-documentation**. Webargs makes it easy to understand the expected arguments and their types for your view functions. * **Automatic documentation**. The metadata that webargs provides can serve as an aid for automatically generating API documentation. * **Cross-framework compatibility**. Webargs provides a consistent request-parsing interface that will work across many Python web frameworks. * **marshmallow integration**. Webargs uses `marshmallow `_ under the hood. When you need more flexibility than dictionaries, you can use marshmallow `Schemas ` to define your request arguments. Get It Now ---------- :: pip install -U webargs Ready to get started? Go on to the :doc:`Quickstart tutorial ` or check out some `examples `_. User Guide ---------- .. toctree:: :maxdepth: 2 install quickstart advanced framework_support ecosystem API Reference ------------- .. toctree:: :maxdepth: 2 api Project Info ------------ .. toctree:: :maxdepth: 1 license changelog upgrading authors contributing python-webargs_8.0.1.orig/docs/install.rst0000644000000000000000000000070013775063555015605 0ustar00Install ======= **webargs** requires Python >= 3.6. It depends on `marshmallow `_ >= 3.0.0. From the PyPI ------------- To install the latest version from the PyPI: :: $ pip install -U webargs Get the Bleeding Edge Version ----------------------------- To get the latest development version of webargs, run :: $ pip install -U git+https://github.com/marshmallow-code/webargs.git@dev python-webargs_8.0.1.orig/docs/license.rst0000644000000000000000000000007113326636200015543 0ustar00 ******* License ******* .. literalinclude:: ../LICENSE python-webargs_8.0.1.orig/docs/make.bat0000644000000000000000000001450213326636200015000 0ustar00@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-webargs_8.0.1.orig/docs/quickstart.rst0000644000000000000000000001657413775063555016351 0ustar00Quickstart ========== Basic Usage ----------- Arguments are specified as a dictionary of name -> :class:`Field ` pairs. .. code-block:: python from webargs import fields, validate user_args = { # Required arguments "username": fields.Str(required=True), # Validation "password": fields.Str(validate=lambda p: len(p) >= 6), # OR use marshmallow's built-in validators "password": fields.Str(validate=validate.Length(min=6)), # Default value when argument is missing "display_per_page": fields.Int(missing=10), # Repeated parameter, e.g. "/?nickname=Fred&nickname=Freddie" "nickname": fields.List(fields.Str()), # Delimited list, e.g. "/?languages=python,javascript" "languages": fields.DelimitedList(fields.Str()), # When value is keyed on a variable-unsafe name # or you want to rename a key "user_type": fields.Str(data_key="user-type"), } .. note:: See the `marshmallow.fields` documentation for a full reference on available field types. To parse request arguments, use the :meth:`parse ` method of a :class:`Parser ` object. .. code-block:: python from flask import request from webargs.flaskparser import parser @app.route("/register", methods=["POST"]) def register(): args = parser.parse(user_args, request) return register_user( args["username"], args["password"], fullname=args["fullname"], per_page=args["display_per_page"], ) Decorator API ------------- As an alternative to `Parser.parse`, you can decorate your view with :meth:`use_args ` or :meth:`use_kwargs `. The parsed arguments dictionary will be injected as a parameter of your view function or as keyword arguments, respectively. .. code-block:: python from webargs.flaskparser import use_args, use_kwargs @app.route("/register", methods=["POST"]) @use_args(user_args) # Injects args dictionary def register(args): return register_user( args["username"], args["password"], fullname=args["fullname"], per_page=args["display_per_page"], ) @app.route("/settings", methods=["POST"]) @use_kwargs(user_args) # Injects keyword arguments def user_settings(username, password, fullname, display_per_page, nickname): return render_template("settings.html", username=username, nickname=nickname) .. note:: When using `use_kwargs`, any missing values will be omitted from the arguments. Use ``**kwargs`` to handle optional arguments. .. code-block:: python from webargs import fields, missing @use_kwargs({"name": fields.Str(required=True), "nickname": fields.Str(required=False)}) def myview(name, **kwargs): if "nickname" not in kwargs: # ... pass Request "Locations" ------------------- By default, webargs will search for arguments from the request body as JSON. You can specify a different location from which to load data like so: .. code-block:: python @app.route("/register") @use_args(user_args, location="form") def register(args): return "registration page" Available locations include: - ``'querystring'`` (same as ``'query'``) - ``'json'`` - ``'form'`` - ``'headers'`` - ``'cookies'`` - ``'files'`` Validation ---------- Each :class:`Field ` object can be validated individually by passing the ``validate`` argument. .. code-block:: python from webargs import fields args = {"age": fields.Int(validate=lambda val: val > 0)} The validator may return either a `boolean` or raise a :exc:`ValidationError `. .. code-block:: python from webargs import fields, ValidationError def must_exist_in_db(val): if not User.query.get(val): # Optionally pass a status_code raise ValidationError("User does not exist") args = {"id": fields.Int(validate=must_exist_in_db)} .. note:: If a validator returns ``None``, validation will pass. A validator must return ``False`` or raise a `ValidationError ` for validation to fail. There are a number of built-in validators from `marshmallow.validate ` (re-exported as `webargs.validate`). .. code-block:: python from webargs import fields, validate args = { "name": fields.Str(required=True, validate=[validate.Length(min=1, max=9999)]), "age": fields.Int(validate=[validate.Range(min=1, max=999)]), } The full arguments dictionary can also be validated by passing ``validate`` to :meth:`Parser.parse `, :meth:`Parser.use_args `, :meth:`Parser.use_kwargs `. .. code-block:: python from webargs import fields from webargs.flaskparser import parser argmap = {"age": fields.Int(), "years_employed": fields.Int()} # ... result = parser.parse( argmap, validate=lambda args: args["years_employed"] < args["age"] ) Error Handling -------------- Each parser has a default error handling method. To override the error handling callback, write a function that receives an error, the request, the `marshmallow.Schema` instance, status code, and headers. Then decorate that function with :func:`Parser.error_handler `. .. code-block:: python from webargs import flaskparser parser = flaskparser.FlaskParser() class CustomError(Exception): pass @parser.error_handler def handle_error(error, req, schema, *, error_status_code, error_headers): raise CustomError(error.messages) Parsing Lists in Query Strings ------------------------------ Use `fields.DelimitedList ` to parse comma-separated lists in query parameters, e.g. ``/?permissions=read,write`` .. code-block:: python from webargs import fields args = {"permissions": fields.DelimitedList(fields.Str())} If you expect repeated query parameters, e.g. ``/?repo=webargs&repo=marshmallow``, use `fields.List ` instead. .. code-block:: python from webargs import fields args = {"repo": fields.List(fields.Str())} Nesting Fields -------------- :class:`Field ` dictionaries can be nested within each other. This can be useful for validating nested data. .. code-block:: python from webargs import fields args = { "name": fields.Nested( {"first": fields.Str(required=True), "last": fields.Str(required=True)} ) } .. note:: Of the default supported locations in webargs, only the ``json`` request location supports nested datastructures. You can, however, :ref:`implement your own data loader ` to add nested field functionality to the other locations. Next Steps ---------- - Go on to :doc:`Advanced Usage ` to learn how to add custom location handlers, use marshmallow Schemas, and more. - See the :doc:`Framework Support ` page for framework-specific guides. - For example applications, check out the `examples `_ directory. python-webargs_8.0.1.orig/docs/upgrading.rst0000644000000000000000000004117414163275625016124 0ustar00Upgrading to Newer Releases =========================== This section documents migration paths to new releases. Upgrading to 8.0 ++++++++++++++++ In 8.0, the default values for ``unknown`` were changed. When the location is set to ``json``, ``form``, or ``json_or_form``, the default for ``unknown`` is now ``None``. Previously, the default was ``RAISE``. Because ``RAISE`` is the default value for ``unknown`` on marshmallow schemas, this change only affects usage in which the following conditions are met: * A schema with ``unknown`` set to ``INCLUDE`` or ``EXCLUDE`` is passed to webargs ``use_args``, ``use_kwargs``, or ``parse`` * ``unknown`` is not passed explicitly to the webargs function * ``location`` is not set (default of ``json``) or is set explicitly to ``json``, ``form``, or ``json_or__form`` For example .. code-block:: python import marshmallow as ma class BodySchema(ma.Schema): foo = ma.fields.String() class Meta: unknown = ma.EXCLUDE @parser.use_args(BodySchema) def foo(data): ... In this case, under webargs 7.0 the schema ``unknown`` setting of ``EXCLUDE`` would be ignored. Instead, ``unknown=RAISE`` would be used. In webargs 8.0, the schema ``unknown`` is used. To get the webargs 7.0 behavior (overriding the Schema ``unknown``), simply pass ``unknown`` to ``use_args``, as in .. code-block:: python @parser.use_args(BodySchema, unknown=ma.RAISE) def foo(data): ... Upgrading to 7.0 ++++++++++++++++ `unknown` is Now Settable by the Parser --------------------------------------- As of 7.0, `Parsers` have multiple settings for controlling the value for `unknown` which is passed to `schema.load` when parsing. To set unknown behavior on a parser, see the advanced doc on this topic: :ref:`advanced_setting_unknown`. Importantly, by default, any schema setting for `unknown` will be overridden by the `unknown` settings for the parser. In order to use a schema's `unknown` value, set `unknown=None` on the parser. In 6.x versions of webargs, schema values for `unknown` are used, so the `unknown=None` setting is the best way to emulate this. To get identical behavior: .. code-block:: python # assuming you have a schema named MySchema # webargs 6.x @parser.use_args(MySchema) def foo(args): ... # webargs 7.x # as a parameter to use_args or parse @parser.use_args(MySchema, unknown=None) def foo(args): ... # webargs 7.x # as a parser setting # example with flaskparser, but any parser class works parser = FlaskParser(unknown=None) @parser.use_args(MySchema) def foo(args): ... Upgrading to 6.0 ++++++++++++++++ Multiple Locations Are No Longer Supported In A Single Call ----------------------------------------------------------- The default location is JSON/body. Under webargs 5.x, code often did not have to specify a location. Because webargs would parse data from multiple locations automatically, users did not need to specify where a parameter, call it `q`, was passed. `q` could be in a query parameter or in a JSON or form-post body. Now, webargs requires that users specify only one location for data loading per `use_args` call, and `"json"` is the default. If `q` is intended to be a query parameter, the developer must be explicit and rewrite like so: .. code-block:: python # webargs 5.x @parser.use_args({"q": ma.fields.String()}) def foo(args): return some_function(user_query=args.get("q")) # webargs 6.x @parser.use_args({"q": ma.fields.String()}, location="query") def foo(args): return some_function(user_query=args.get("q")) This also means that another usage from 5.x is not supported. Code with multiple locations in a single `use_args`, `use_kwargs`, or `parse` call must be rewritten in multiple separate `use_args` or `use_kwargs` invocations, like so: .. code-block:: python # webargs 5.x @parser.use_kwargs( { "q1": ma.fields.Int(location="query"), "q2": ma.fields.Int(location="query"), "h1": ma.fields.Int(location="headers"), }, locations=("query", "headers"), ) def foo(q1, q2, h1): ... # webargs 6.x @parser.use_kwargs({"q1": ma.fields.Int(), "q2": ma.fields.Int()}, location="query") @parser.use_kwargs({"h1": ma.fields.Int()}, location="headers") def foo(q1, q2, h1): ... Fields No Longer Support location=... ------------------------------------- Because a single `parser.use_args`, `parser.use_kwargs`, or `parser.parse` call cannot specify multiple locations, it is not necessary for a field to be able to specify its location. Rewrite code like so: .. code-block:: python # webargs 5.x @parser.use_args({"q": ma.fields.String(location="query")}) def foo(args): return some_function(user_query=args.get("q")) # webargs 6.x @parser.use_args({"q": ma.fields.String()}, location="query") def foo(args): return some_function(user_query=args.get("q")) location_handler Has Been Replaced With location_loader ------------------------------------------------------- This is not just a name change. The expected signature of a `location_loader` is slightly different from the signature for a `location_handler`. Where previously a `location_handler` code took the incoming request data and details of a single field being loaded, a `location_loader` takes the request and the schema as a pair. It does not return a specific field's data, but data for the whole location. Rewrite code like this: .. code-block:: python # webargs 5.x @parser.location_handler("data") def load_data(request, name, field): return request.data.get(name) # webargs 6.x @parser.location_loader("data") def load_data(request, schema): return request.data Data Is Not Filtered Before Being Passed To Schemas, And It May Be Proxified ---------------------------------------------------------------------------- In webargs 5.x, the deserialization schema was used to pull data out of the request object. That data was compiled into a dictionary which was then passed to the schema. One of the major changes in webargs 6.x allows the use of `unknown` parameter on schemas. This lets a schema decide what to do with fields not specified in the schema. In order to achieve this, webargs now passes the full data from the specified location to the schema. Therefore, users should specify `unknown=marshmallow.EXCLUDE` on their schemas in order to filter out unknown fields. Like so: .. code-block:: python # webargs 5.x # this can assume that "q" is the only parameter passed, and all other # parameters will be ignored @parser.use_kwargs({"q": ma.fields.String()}, locations=("query",)) def foo(q): ... # webargs 6.x, Solution 1: declare a schema with Meta.unknown set class QuerySchema(ma.Schema): q = ma.fields.String() class Meta: unknown = ma.EXCLUDE @parser.use_kwargs(QuerySchema, location="query") def foo(q): ... # webargs 6.x, Solution 2: instantiate a schema with unknown set class QuerySchema(ma.Schema): q = ma.fields.String() @parser.use_kwargs(QuerySchema(unknown=ma.EXCLUDE), location="query") def foo(q): ... This also allows usage which passes the unknown parameters through, like so: .. code-block:: python # webargs 6.x only! cannot be done in 5.x class QuerySchema(ma.Schema): q = ma.fields.String() # will pass *all* query params through as "kwargs" @parser.use_kwargs(QuerySchema(unknown=ma.INCLUDE), location="query") def foo(q, **kwargs): ... However, many types of request data are so-called "multidicts" -- dictionary-like types which can return one or multiple values. To handle `marshmallow.fields.List` and `webargs.fields.DelimitedList` fields correctly, passing list data, webargs must combine schema information with the raw request data. This is done in the :class:`MultiDictProxy ` type, which will often be passed to schemas. This means that if a schema has a `pre_load` hook which interacts with the data, it may need modifications. For example, a `flask` query string will be parsed into an `ImmutableMultiDict` type, which will break pre-load hooks which modify the data in-place. Such usages need rewrites like so: .. code-block:: python # webargs 5.x # flask query params is just an example -- applies to several types from webargs.flaskparser import use_kwargs class QuerySchema(ma.Schema): q = ma.fields.String() @ma.pre_load def convert_nil_to_none(self, obj, **kwargs): if obj.get("q") == "nil": obj["q"] = None return obj @use_kwargs(QuerySchema, locations=("query",)) def foo(q): ... # webargs 6.x class QuerySchema(ma.Schema): q = ma.fields.String() # unlike under 5.x, we cannot modify 'obj' in-place because writing # to the MultiDictProxy will try to write to the underlying # ImmutableMultiDict, which is not allowed @ma.pre_load def convert_nil_to_none(self, obj, **kwargs): # creating a dict from a MultiDictProxy works well because it # "unwraps" lists and delimited lists correctly data = dict(obj) if data.get("q") == "nil": data["q"] = None return data @parser.use_kwargs(QuerySchema, location="query") def foo(q): ... DelimitedList Now Only Takes A String Input ------------------------------------------- Combining `List` and string parsing functionality in a single type had some messy corner cases. For the most part, this should not require rewrites. But for APIs which need to allow both usages, rewrites are possible like so: .. code-block:: python # webargs 5.x # this allows ...?x=1&x=2&x=3 # as well as ...?x=1,2,3 @use_kwargs({"x": webargs.fields.DelimitedList(ma.fields.Int)}, locations=("query",)) def foo(x): ... # webargs 6.x # this accepts x=1,2,3 but NOT x=1&x=2&x=3 @use_kwargs({"x": webargs.fields.DelimitedList(ma.fields.Int)}, location="query") def foo(x): ... # webargs 6.x # this accepts x=1,2,3 ; x=1&x=2&x=3 ; x=1,2&x=3 # to do this, it needs a post_load hook which will flatten out the list data class UnpackingDelimitedListSchema(ma.Schema): x = ma.fields.List(webargs.fields.DelimitedList(ma.fields.Int)) @ma.post_load def flatten_lists(self, data, **kwargs): new_x = [] for x in data["x"]: new_x.extend(x) data["x"] = new_x return data @parser.use_kwargs(UnpackingDelimitedListSchema, location="query") def foo(x): ... ValidationError Messages Are Namespaced Under The Location ---------------------------------------------------------- Code parsing ValidationError messages will notice a change in the messages produced by webargs. What would previously have come back with messages like `{"foo":["Not a valid integer."]}` will now have messages nested one layer deeper, like `{"json":{"foo":["Not a valid integer."]}}`. To rewrite code which was handling these errors, the handler will need to be prepared to traverse messages by one additional level. For example: .. code-block:: python import logging log = logging.getLogger(__name__) # webargs 5.x # logs debug messages like # bad value for 'foo': ["Not a valid integer."] # bad value for 'bar': ["Not a valid boolean."] def log_invalid_parameters(validation_error): for field, messages in validation_error.messages.items(): log.debug("bad value for '{}': {}".format(field, messages)) # webargs 6.x # logs debug messages like # bad value for 'foo' [query]: ["Not a valid integer."] # bad value for 'bar' [json]: ["Not a valid boolean."] def log_invalid_parameters(validation_error): for location, fielddata in validation_error.messages.items(): for field, messages in fielddata.items(): log.debug("bad value for '{}' [{}]: {}".format(field, location, messages)) Custom Error Handler Argument Names Changed ------------------------------------------- If you define a custom error handler via `@parser.error_handler` the function arguments are now keyword-only and `status_code` and `headers` have been renamed `error_status_code` and `error_headers`. .. code-block:: python # webargs 5.x @parser.error_handler def custom_handle_error(error, req, schema, status_code, headers): ... # webargs 6.x @parser.error_handler def custom_handle_error(error, req, schema, *, error_status_code, error_headers): ... Some Functions Take Keyword-Only Arguments Now ---------------------------------------------- The signature of several methods has changed to have keyword-only arguments. For the most part, this should not require any changes, but here's a list of the changes. `parser.error_handler` methods: .. code-block:: python # webargs 5.x def handle_error(error, req, schema, status_code, headers): ... # webargs 6.x def handle_error(error, req, schema, *, error_status_code, error_headers): ... `parser.__init__` methods: .. code-block:: python # webargs 5.x def __init__(self, location=None, error_handler=None, schema_class=None): ... # webargs 6.x def __init__(self, location=None, *, error_handler=None, schema_class=None): ... `parser.parse`, `parser.use_args`, and `parser.use_kwargs` methods: .. code-block:: python # webargs 5.x def parse( self, argmap, req=None, location=None, validate=None, error_status_code=None, error_headers=None, ): ... # webargs 6.x def parse( self, argmap, req=None, *, location=None, validate=None, error_status_code=None, error_headers=None ): ... # webargs 5.x def use_args( self, argmap, req=None, location=None, as_kwargs=False, validate=None, error_status_code=None, error_headers=None, ): ... # webargs 6.x def use_args( self, argmap, req=None, *, location=None, as_kwargs=False, validate=None, error_status_code=None, error_headers=None ): ... # use_kwargs is just an alias for use_args with as_kwargs=True and finally, the `dict2schema` function: .. code-block:: python # webargs 5.x def dict2schema(dct, schema_class=ma.Schema): ... # webargs 6.x def dict2schema(dct, *, schema_class=ma.Schema): ... PyramidParser Now Appends Arguments (Used To Prepend) ----------------------------------------------------- `PyramidParser.use_args` was not conformant with the other parsers in webargs. While all other parsers added new arguments to the end of the argument list of a decorated view function, the Pyramid implementation added them to the front of the argument list. This has been corrected, but as a result pyramid views with `use_args` may need to be rewritten. The `request` object is always passed first in both versions, so the issue is only apparent with view functions taking other positional arguments. For example, imagine code with a decorator for passing user information, `pass_userinfo`, like so: .. code-block:: python # a decorator which gets information about the authenticated user def pass_userinfo(f): def decorator(request, *args, **kwargs): return f(request, get_userinfo(), *args, **kwargs) return decorator You will see a behavioral change if `pass_userinfo` is called on a function decorated with `use_args`. The difference between the two versions will be like so: .. code-block:: python from webargs.pyramidparser import use_args # webargs 5.x # pass_userinfo is called first, webargs sees positional arguments of # (userinfo,) # and changes it to # (request, args, userinfo) @pass_userinfo @use_args({"q": ma.fields.String()}, locations=("query",)) def viewfunc(request, args, userinfo): q = args.get("q") ... # webargs 6.x # pass_userinfo is called first, webargs sees positional arguments of # (userinfo,) # and changes it to # (request, userinfo, args) @pass_userinfo @use_args({"q": ma.fields.String()}, location="query") def viewfunc(request, userinfo, args): q = args.get("q") ... python-webargs_8.0.1.orig/docs/_static/logo.png0000644000000000000000000001605514163275625016506 0ustar00PNG  IHDR>UIDATx^y|TdOK!X" [VubŶԷ- KU ,AEY@H~L,$;sgnΝ?9~ws Ŵp P 01 Σ$@3H(`; M95K=mx]e ţ͛Z!9 ax p}1yӴ#\\`d=<+?xթO?~rX-جVą7W}`k[w@qy~ՆQm;?>*^:,p}<Ա7~}}j}>7 eB 낹?u}bVz Z X1qOQ)򲫾mAݺ#G?1;h +8#^~bk޿aؒ~y((wh&`) bBqkD|tb֡bMalM?Ғ _9ڂ0u"" 6< c;`:NdN&eS=XlbPl6pmj }Wl\d8JeWTJ U,X툏h!:18<D|~]Day)Wv9!?ƈ0cz,e FQYi5E[Va o'8&6rKn^fXG7 AǨ昘xa(}Uw؏ 9(( C&YDW(oxzЊiRԑnJ5:@+VU64i_uh8r9ၯ(`Wgűic@K9 ŧ%R݇vII ;cKZܱWPT^Z{n}e \0]\k&t_6wwہΙe>p:jC0LIi϶|,O¼jKBrӟ\UK1''^q4<@? tGƕkuNPi׏ved9ڴ% (|kSq4W$, \͎ ߾ۭ_@vIQw'g;ťY-l|KlUe1SSħi䕖YNU7rsA:oƅ")˿Req% }?_VǦaQ96u ,=7@߁ 9q"W9C7bؚ~'ru-'SO C\r q]d4lIN#5?/u \34\ 䃶 oqI9pp Eee, }R@wd/V;JO*KgYQb@ƭF)`-2P6rƝqIXW4OmMN9I Z.+ h{MwMTBȓ6+S8v~եq?hl(\!.)&u7BOg{l7;r~X ?\W oG#DS/sAՍ Lbik#| ws9eSSb~=iyms :fGߘ!Sp$ *?:wƍCy9!Yp(l~>H`gu#\uK˃&`ps EJ=2Ґ]\ r]mKՍ C#pSX<nz_lK?39'sؙsAZvX01n73 {̫^ Nl(m Bs#Ϸ& g!5/rKOYL$%a h:` }'52 sWZnXj ȡɾze'p[0ڄE!/N75YM;ئjF Lk$蚭KOQwϳD|oosPZg5c\rKnڑԤ%d9&ڈ%=&(g@mhN{ 1m&>ctv_X뢚ĩuě6#Uou;5׶&8>ZҙKg3WunAS]΃:<&9U$~!A%>}ȔuXMBq׫e+.p\C@ZIa~V _Z$$)`OhUk+`{vN~?&N_1gPV|XNr5_}^\zreM(. ) lmvH>}W Q"ʔy\u-Zm _3_U䞤TmߴPl>{Q\^Jj&K @8,σEvq(Ol *ZF`X뎘C~.\Z7[l̢<%2sңyp4k z):̢|%]5"ѫy&?~QI=S{uɷUTkM[6Ӷ^;pkXw(d#ױp_,`{0DŽv]L !7KLvKO7YQ@e ߝ:5Z1-jXzǯ\rLJV~}f\(`לD6Ӯ+^38 b٩8mg* ص,<$u ZONDfI!ظ1="@^ 6* oXvَ0&0TbjҋʴϗmFXǪXn8ٹCo%RJ.nY:S:k6~-xW>\#>^#gฮ}ct2 M3Vn^l?|Gس^L?E5eF R cI0k^׵c\/+£l;2$?wFc,3 p mxXy'OrWZP;c|ϕ1r=7ݦQZnbwF.#,+'a s +gCӑQ%jge\ DߘXVT>.faǥ4\*]%76Brң-7ax|^5TܽlR8}3)WbN*6$=bbwyô> vkӎ̳WS2nqs ;Vg ((ӷ>'!7bؼc1ȚR`za ȑ_xœ?R!2u_mjފj_}Xz's/{{WI$E5z`ZѦ򕩌+KY$܏L/>N]B͂B1#m|!3_c҆b)r'ѾW~)dXI{Vg&!`1%֝;2>e ѧylL@qG@Zgq(3˴/}7ع.?ɼTZ\:mJ vyP~VdUfuh ȕlG8}յIK7蔭KĒSQu X.b\eU[ӮRӇYTPqzK{/`ڄGabb/߼fj XsKiH/C@꣼{W_r}}Za鈟^i$mtz}I@U:EZ PZI (HV)4b;PSh h%@k%v$  XA$JJH@ANI$ۑ(`BH@+ X+)# P :&VVRlG t M"(`؎$@+DZ PZI (HV)4b;PSh h%@k%v$  XA$JJH@ANI$ۑ(`BH@+ X+)# P :&VVRlG t M"(`؎$@+DZ PZI (HV)4b;PSh h%@k%v$  XA$JJH@ANI$ۑ(`BH@+ X+)# P :&VVRlG t M"(`؎$@+DZ PZI (HV)4b;PSh h%@k%v$  XA$JJH@ANI$ۑ(`BH@+ X+)# P :&V4UpIENDB`python-webargs_8.0.1.orig/docs/_templates/donate.html0000644000000000000000000000024013421337646017672 0ustar00{% if donate_url %} {% endif %} python-webargs_8.0.1.orig/docs/_templates/sponsors.html0000644000000000000000000000067113433772441020315 0ustar00
{% if tidelift_url %} {% endif %}
python-webargs_8.0.1.orig/examples/__init__.py0000644000000000000000000000000013326636200016356 0ustar00python-webargs_8.0.1.orig/examples/aiohttp_example.py0000644000000000000000000000457713775063555020050 0ustar00"""A simple number and datetime addition JSON API. Run the app: $ python examples/aiohttp_example.py Try the following with httpie (a cURL-like utility, http://httpie.org): $ pip install httpie $ http GET :5001/ $ http GET :5001/ name==Ada $ http POST :5001/add x=40 y=2 $ http POST :5001/dateadd value=1973-04-10 addend=63 $ http POST :5001/dateadd value=2014-10-23 addend=525600 unit=minutes """ import asyncio import datetime as dt from aiohttp import web from aiohttp.web import json_response from webargs import fields, validate from webargs.aiohttpparser import use_args, use_kwargs hello_args = {"name": fields.Str(missing="Friend")} @use_args(hello_args) async def index(request, args): """A welcome page.""" return json_response({"message": "Welcome, {}!".format(args["name"])}) add_args = {"x": fields.Float(required=True), "y": fields.Float(required=True)} @use_kwargs(add_args) async def add(request, x, y): """An addition endpoint.""" return json_response({"result": x + y}) dateadd_args = { "value": fields.Date(required=False), "addend": fields.Int(required=True, validate=validate.Range(min=1)), "unit": fields.Str(missing="days", validate=validate.OneOf(["minutes", "days"])), } @use_kwargs(dateadd_args) async def dateadd(request, value, addend, unit): """A datetime adder endpoint.""" value = value or dt.datetime.utcnow() if unit == "minutes": delta = dt.timedelta(minutes=addend) else: delta = dt.timedelta(days=addend) result = value + delta return json_response({"result": result.isoformat()}) def create_app(): app = web.Application() app.router.add_route("GET", "/", index) app.router.add_route("POST", "/add", add) app.router.add_route("POST", "/dateadd", dateadd) return app def run(app, port=5001): loop = asyncio.get_event_loop() handler = app.make_handler() f = loop.create_server(handler, "0.0.0.0", port) srv = loop.run_until_complete(f) print("serving on", srv.sockets[0].getsockname()) try: loop.run_forever() except KeyboardInterrupt: pass finally: loop.run_until_complete(handler.finish_connections(1.0)) srv.close() loop.run_until_complete(srv.wait_closed()) loop.run_until_complete(app.finish()) loop.close() if __name__ == "__main__": app = create_app() run(app) python-webargs_8.0.1.orig/examples/annotations_example.py0000644000000000000000000000733013775063555020723 0ustar00"""Example of using Python 3 function annotations to define request arguments and output schemas. Run the app: $ python examples/annotations_example.py Try the following with httpie (a cURL-like utility, http://httpie.org): $ pip install httpie $ http GET :5001/ $ http GET :5001/ name==Ada $ http POST :5001/add x=40 y=2 $ http GET :5001/users/42 """ import random import functools from flask import Flask, request from marshmallow import Schema from webargs import fields from webargs.flaskparser import parser app = Flask(__name__) ##### Routing wrapper #### def route(*args, **kwargs): """Combines `Flask.route` and webargs parsing. Allows arguments to be specified as function annotations. An output schema can optionally be specified by a return annotation. """ def decorator(func): @app.route(*args, **kwargs) @functools.wraps(func) def wrapped_view(*a, **kw): annotations = getattr(func, "__annotations__", {}) reqargs = { name: value for name, value in annotations.items() if isinstance(value, fields.Field) and name != "return" } response_schema = annotations.get("return") schema_cls = Schema.from_dict(reqargs) partial = request.method != "POST" parsed = parser.parse(schema_cls(partial=partial), request) kw.update(parsed) response_data = func(*a, **kw) if response_schema: return response_schema.dump(response_data) else: return func(*a, **kw) return wrapped_view return decorator ##### Fake database and model ##### class Model: def __init__(self, **kwargs): self.__dict__.update(kwargs) def update(self, **kwargs): self.__dict__.update(kwargs) @classmethod def insert(cls, db, **kwargs): collection = db[cls.collection] new_id = None if "id" in kwargs: # for setting up fixtures new_id = kwargs.pop("id") else: # find a new id found_id = False while not found_id: new_id = random.randint(1, 9999) if new_id not in collection: found_id = True new_record = cls(id=new_id, **kwargs) collection[new_id] = new_record return new_record class User(Model): collection = "users" db = {"users": {}} ##### Views ##### @route("/", methods=["GET"]) def index(name: fields.Str(missing="Friend")): # noqa: F821 return {"message": f"Hello, {name}!"} @route("/add", methods=["POST"]) def add(x: fields.Float(required=True), y: fields.Float(required=True)): return {"result": x + y} class UserSchema(Schema): id = fields.Int(dump_only=True) username = fields.Str(required=True) first_name = fields.Str() last_name = fields.Str() @route("/users/", methods=["GET", "PATCH"]) def user_detail(user_id, username: fields.Str(required=True) = None) -> UserSchema(): user = db["users"].get(user_id) if not user: return {"message": "User not found"}, 404 if request.method == "PATCH": user.update(username=username) return user # Return validation errors as JSON @app.errorhandler(422) @app.errorhandler(400) def handle_error(err): headers = err.data.get("headers", None) messages = err.data.get("messages", ["Invalid request."]) if headers: return {"errors": messages}, err.code, headers else: return {"errors": messages}, err.code if __name__ == "__main__": User.insert( db=db, id=42, username="fred", first_name="Freddie", last_name="Mercury" ) app.run(port=5001, debug=True) python-webargs_8.0.1.orig/examples/bottle_example.py0000644000000000000000000000345513775063555017663 0ustar00"""A simple number and datetime addition JSON API. Run the app: $ python examples/bottle_example.py Try the following with httpie (a cURL-like utility, http://httpie.org): $ pip install httpie $ http GET :5001/ $ http GET :5001/ name==Ada $ http POST :5001/add x=40 y=2 $ http POST :5001/dateadd value=1973-04-10 addend=63 $ http POST :5001/dateadd value=2014-10-23 addend=525600 unit=minutes """ import datetime as dt from bottle import route, run, error, response from webargs import fields, validate from webargs.bottleparser import use_args, use_kwargs hello_args = {"name": fields.Str(missing="Friend")} @route("/", method="GET", apply=use_args(hello_args)) def index(args): """A welcome page.""" return {"message": "Welcome, {}!".format(args["name"])} add_args = {"x": fields.Float(required=True), "y": fields.Float(required=True)} @route("/add", method="POST", apply=use_kwargs(add_args)) def add(x, y): """An addition endpoint.""" return {"result": x + y} dateadd_args = { "value": fields.Date(required=False), "addend": fields.Int(required=True, validate=validate.Range(min=1)), "unit": fields.Str(missing="days", validate=validate.OneOf(["minutes", "days"])), } @route("/dateadd", method="POST", apply=use_kwargs(dateadd_args)) def dateadd(value, addend, unit): """A date adder endpoint.""" value = value or dt.datetime.utcnow() if unit == "minutes": delta = dt.timedelta(minutes=addend) else: delta = dt.timedelta(days=addend) result = value + delta return {"result": result.isoformat()} # Return validation errors as JSON @error(400) @error(422) def handle_error(err): response.content_type = "application/json" return err.body if __name__ == "__main__": run(port=5001, reloader=True, debug=True) python-webargs_8.0.1.orig/examples/falcon_example.py0000644000000000000000000000503713775063555017632 0ustar00"""A simple number and datetime addition JSON API. Demonstrates different strategies for parsing arguments with the FalconParser. Run the app: $ pip install gunicorn $ gunicorn examples.falcon_example:app Try the following with httpie (a cURL-like utility, http://httpie.org): $ pip install httpie $ http GET :8000/ $ http GET :8000/ name==Ada $ http POST :8000/add x=40 y=2 $ http POST :8000/dateadd value=1973-04-10 addend=63 $ http POST :8000/dateadd value=2014-10-23 addend=525600 unit=minutes """ import datetime as dt from webargs.core import json import falcon from webargs import fields, validate from webargs.falconparser import use_args, use_kwargs, parser ### Middleware and hooks ### class JSONTranslator: def process_response(self, req, resp, resource): if "result" not in req.context: return resp.body = json.dumps(req.context["result"]) def add_args(argmap, **kwargs): def hook(req, resp, params): req.context["args"] = parser.parse(argmap, req=req, **kwargs) return hook ### Resources ### class HelloResource: """A welcome page.""" hello_args = {"name": fields.Str(missing="Friend", location="query")} @use_args(hello_args) def on_get(self, req, resp, args): req.context["result"] = {"message": "Welcome, {}!".format(args["name"])} class AdderResource: """An addition endpoint.""" adder_args = {"x": fields.Float(required=True), "y": fields.Float(required=True)} @use_kwargs(adder_args) def on_post(self, req, resp, x, y): req.context["result"] = {"result": x + y} class DateAddResource: """A datetime adder endpoint.""" dateadd_args = { "value": fields.Date(required=False), "addend": fields.Int(required=True, validate=validate.Range(min=1)), "unit": fields.Str( missing="days", validate=validate.OneOf(["minutes", "days"]) ), } @falcon.before(add_args(dateadd_args)) def on_post(self, req, resp): """A datetime adder endpoint.""" args = req.context["args"] value = args["value"] or dt.datetime.utcnow() if args["unit"] == "minutes": delta = dt.timedelta(minutes=args["addend"]) else: delta = dt.timedelta(days=args["addend"]) result = value + delta req.context["result"] = {"result": result.isoformat()} app = falcon.API(middleware=[JSONTranslator()]) app.add_route("/", HelloResource()) app.add_route("/add", AdderResource()) app.add_route("/dateadd", DateAddResource()) python-webargs_8.0.1.orig/examples/flask_example.py0000644000000000000000000000402413775063555017463 0ustar00"""A simple number and datetime addition JSON API. Run the app: $ python examples/flask_example.py Try the following with httpie (a cURL-like utility, http://httpie.org): $ pip install httpie $ http GET :5001/ $ http GET :5001/ name==Ada $ http POST :5001/add x=40 y=2 $ http POST :5001/dateadd value=1973-04-10 addend=63 $ http POST :5001/dateadd value=2014-10-23 addend=525600 unit=minutes """ import datetime as dt from flask import Flask, jsonify from webargs import fields, validate from webargs.flaskparser import use_args, use_kwargs app = Flask(__name__) hello_args = {"name": fields.Str(missing="Friend")} @app.route("/", methods=["GET"]) @use_args(hello_args) def index(args): """A welcome page.""" return jsonify({"message": "Welcome, {}!".format(args["name"])}) add_args = {"x": fields.Float(required=True), "y": fields.Float(required=True)} @app.route("/add", methods=["POST"]) @use_kwargs(add_args) def add(x, y): """An addition endpoint.""" return jsonify({"result": x + y}) dateadd_args = { "value": fields.Date(required=False), "addend": fields.Int(required=True, validate=validate.Range(min=1)), "unit": fields.Str(missing="days", validate=validate.OneOf(["minutes", "days"])), } @app.route("/dateadd", methods=["POST"]) @use_kwargs(dateadd_args) def dateadd(value, addend, unit): """A date adder endpoint.""" value = value or dt.datetime.utcnow() if unit == "minutes": delta = dt.timedelta(minutes=addend) else: delta = dt.timedelta(days=addend) result = value + delta return jsonify({"result": result.isoformat()}) # Return validation errors as JSON @app.errorhandler(422) @app.errorhandler(400) def handle_error(err): headers = err.data.get("headers", None) messages = err.data.get("messages", ["Invalid request."]) if headers: return jsonify({"errors": messages}), err.code, headers else: return jsonify({"errors": messages}), err.code if __name__ == "__main__": app.run(port=5001, debug=True) python-webargs_8.0.1.orig/examples/flaskrestful_example.py0000644000000000000000000000452413775063555021075 0ustar00"""A simple number and datetime addition JSON API. Run the app: $ python examples/flaskrestful_example.py Try the following with httpie (a cURL-like utility, http://httpie.org): $ pip install httpie $ http GET :5001/ $ http GET :5001/ name==Ada $ http POST :5001/add x=40 y=2 $ http POST :5001/dateadd value=1973-04-10 addend=63 $ http POST :5001/dateadd value=2014-10-23 addend=525600 unit=minutes """ import datetime as dt from flask import Flask from flask_restful import Api, Resource from webargs import fields, validate from webargs.flaskparser import use_args, use_kwargs, parser, abort app = Flask(__name__) api = Api(app) class IndexResource(Resource): """A welcome page.""" hello_args = {"name": fields.Str(missing="Friend")} @use_args(hello_args) def get(self, args): return {"message": "Welcome, {}!".format(args["name"])} class AddResource(Resource): """An addition endpoint.""" add_args = {"x": fields.Float(required=True), "y": fields.Float(required=True)} @use_kwargs(add_args) def post(self, x, y): """An addition endpoint.""" return {"result": x + y} class DateAddResource(Resource): dateadd_args = { "value": fields.Date(required=False), "addend": fields.Int(required=True, validate=validate.Range(min=1)), "unit": fields.Str( missing="days", validate=validate.OneOf(["minutes", "days"]) ), } @use_kwargs(dateadd_args) def post(self, value, addend, unit): """A date adder endpoint.""" value = value or dt.datetime.utcnow() if unit == "minutes": delta = dt.timedelta(minutes=addend) else: delta = dt.timedelta(days=addend) result = value + delta return {"result": result.isoformat()} # This error handler is necessary for usage with Flask-RESTful @parser.error_handler def handle_request_parsing_error(err, req, schema, *, error_status_code, error_headers): """webargs error handler that uses Flask-RESTful's abort function to return a JSON error response to the client. """ abort(error_status_code, errors=err.messages) if __name__ == "__main__": api.add_resource(IndexResource, "/") api.add_resource(AddResource, "/add") api.add_resource(DateAddResource, "/dateadd") app.run(port=5001, debug=True) python-webargs_8.0.1.orig/examples/pyramid_example.py0000644000000000000000000000447113775063555020036 0ustar00"""A simple number and datetime addition JSON API. Run the app: $ python examples/pyramid_example.py Try the following with httpie (a cURL-like utility, http://httpie.org): $ pip install httpie $ http GET :5001/ $ http GET :5001/ name==Ada $ http POST :5001/add x=40 y=2 $ http POST :5001/dateadd value=1973-04-10 addend=63 $ http POST :5001/dateadd value=2014-10-23 addend=525600 unit=minutes """ import datetime as dt from wsgiref.simple_server import make_server from pyramid.config import Configurator from pyramid.view import view_config from pyramid.renderers import JSON from webargs import fields, validate from webargs.pyramidparser import use_args, use_kwargs hello_args = {"name": fields.Str(missing="Friend")} @view_config(route_name="hello", request_method="GET", renderer="json") @use_args(hello_args) def index(request, args): """A welcome page.""" return {"message": "Welcome, {}!".format(args["name"])} add_args = {"x": fields.Float(required=True), "y": fields.Float(required=True)} @view_config(route_name="add", request_method="POST", renderer="json") @use_kwargs(add_args) def add(request, x, y): """An addition endpoint.""" return {"result": x + y} dateadd_args = { "value": fields.Date(required=False), "addend": fields.Int(required=True, validate=validate.Range(min=1)), "unit": fields.Str(missing="days", validate=validate.OneOf(["minutes", "days"])), } @view_config(route_name="dateadd", request_method="POST", renderer="json") @use_kwargs(dateadd_args) def dateadd(request, value, addend, unit): """A date adder endpoint.""" value = value or dt.datetime.utcnow() if unit == "minutes": delta = dt.timedelta(minutes=addend) else: delta = dt.timedelta(days=addend) result = value + delta return {"result": result} if __name__ == "__main__": config = Configurator() json_renderer = JSON() json_renderer.add_adapter(dt.datetime, lambda v, request: v.isoformat()) config.add_renderer("json", json_renderer) config.add_route("hello", "/") config.add_route("add", "/add") config.add_route("dateadd", "/dateadd") config.scan(__name__) app = config.make_wsgi_app() port = 5001 server = make_server("0.0.0.0", port, app) print(f"Serving on port {port}") server.serve_forever() python-webargs_8.0.1.orig/examples/requirements.txt0000644000000000000000000000010214163275625017546 0ustar00python-dateutil==2.8.2 Flask bottle tornado flask-restful pyramid python-webargs_8.0.1.orig/examples/schema_example.py0000644000000000000000000000742613775063555017634 0ustar00"""Example implementation of using a marshmallow Schema for both request input and output with a `use_schema` decorator. Run the app: $ python examples/schema_example.py Try the following with httpie (a cURL-like utility, http://httpie.org): $ pip install httpie $ http GET :5001/users/ $ http GET :5001/users/42 $ http POST :5001/users/ username=brian first_name=Brian last_name=May $ http PATCH :5001/users/42 username=freddie $ http GET :5001/users/ limit==1 """ import functools from flask import Flask, request import random from marshmallow import Schema, fields, post_dump from webargs.flaskparser import parser, use_kwargs app = Flask(__name__) ##### Fake database and model ##### class Model: def __init__(self, **kwargs): self.__dict__.update(kwargs) def update(self, **kwargs): self.__dict__.update(kwargs) @classmethod def insert(cls, db, **kwargs): collection = db[cls.collection] new_id = None if "id" in kwargs: # for setting up fixtures new_id = kwargs.pop("id") else: # find a new id found_id = False while not found_id: new_id = random.randint(1, 9999) if new_id not in collection: found_id = True new_record = cls(id=new_id, **kwargs) collection[new_id] = new_record return new_record class User(Model): collection = "users" db = {"users": {}} ##### use_schema ##### def use_schema(schema_cls, list_view=False, locations=None): """View decorator for using a marshmallow schema to (1) parse a request's input and (2) serializing the view's output to a JSON response. """ def decorator(func): @functools.wraps(func) def wrapped(*args, **kwargs): partial = request.method != "POST" schema = schema_cls(partial=partial) use_args_wrapper = parser.use_args(schema, locations=locations) # Function wrapped with use_args func_with_args = use_args_wrapper(func) ret = func_with_args(*args, **kwargs) return schema.dump(ret, many=list_view) return wrapped return decorator ##### Schemas ##### class UserSchema(Schema): id = fields.Int(dump_only=True) username = fields.Str(required=True) first_name = fields.Str() last_name = fields.Str() @post_dump(pass_many=True) def wrap_with_envelope(self, data, many, **kwargs): return {"data": data} ##### Routes ##### @app.route("/users/", methods=["GET", "PATCH"]) @use_schema(UserSchema) def user_detail(reqargs, user_id): user = db["users"].get(user_id) if not user: return {"message": "User not found"}, 404 if request.method == "PATCH" and reqargs: user.update(**reqargs) return user # You can add additional arguments with use_kwargs @app.route("/users/", methods=["GET", "POST"]) @use_kwargs({"limit": fields.Int(missing=10, location="query")}) @use_schema(UserSchema, list_view=True) def user_list(reqargs, limit): users = db["users"].values() if request.method == "POST": User.insert(db=db, **reqargs) return list(users)[:limit] # Return validation errors as JSON @app.errorhandler(422) @app.errorhandler(400) def handle_validation_error(err): exc = getattr(err, "exc", None) if exc: headers = err.data["headers"] messages = exc.messages else: headers = None messages = ["Invalid request."] if headers: return {"errors": messages}, err.code, headers else: return {"errors": messages}, err.code if __name__ == "__main__": User.insert( db=db, id=42, username="fred", first_name="Freddie", last_name="Mercury" ) app.run(port=5001, debug=True) python-webargs_8.0.1.orig/examples/tornado_example.py0000644000000000000000000000521713775063555020036 0ustar00"""A simple number and datetime addition JSON API. Run the app: $ python examples/tornado_example.py Try the following with httpie (a cURL-like utility, http://httpie.org): $ pip install httpie $ http GET :5001/ $ http GET :5001/ name==Ada $ http POST :5001/add x=40 y=2 $ http POST :5001/dateadd value=1973-04-10 addend=63 $ http POST :5001/dateadd value=2014-10-23 addend=525600 unit=minutes """ import datetime as dt import tornado.ioloop from tornado.web import RequestHandler from webargs import fields, validate from webargs.tornadoparser import use_args, use_kwargs class BaseRequestHandler(RequestHandler): def write_error(self, status_code, **kwargs): """Write errors as JSON.""" self.set_header("Content-Type", "application/json") if "exc_info" in kwargs: etype, exc, traceback = kwargs["exc_info"] if hasattr(exc, "messages"): self.write({"errors": exc.messages}) if getattr(exc, "headers", None): for name, val in exc.headers.items(): self.set_header(name, val) self.finish() class HelloHandler(BaseRequestHandler): """A welcome page.""" hello_args = {"name": fields.Str(missing="Friend")} @use_args(hello_args) def get(self, args): response = {"message": "Welcome, {}!".format(args["name"])} self.write(response) class AdderHandler(BaseRequestHandler): """An addition endpoint.""" add_args = {"x": fields.Float(required=True), "y": fields.Float(required=True)} @use_kwargs(add_args) def post(self, x, y): self.write({"result": x + y}) class DateAddHandler(BaseRequestHandler): """A date adder endpoint.""" dateadd_args = { "value": fields.Date(required=False), "addend": fields.Int(required=True, validate=validate.Range(min=1)), "unit": fields.Str( missing="days", validate=validate.OneOf(["minutes", "days"]) ), } @use_kwargs(dateadd_args) def post(self, value, addend, unit): """A date adder endpoint.""" value = value or dt.datetime.utcnow() if unit == "minutes": delta = dt.timedelta(minutes=addend) else: delta = dt.timedelta(days=addend) result = value + delta self.write({"result": result.isoformat()}) if __name__ == "__main__": app = tornado.web.Application( [(r"/", HelloHandler), (r"/add", AdderHandler), (r"/dateadd", DateAddHandler)], debug=True, ) port = 5001 app.listen(port) print(f"Serving on port {port}") tornado.ioloop.IOLoop.instance().start() python-webargs_8.0.1.orig/src/webargs/0000755000000000000000000000000013654272604014672 5ustar00python-webargs_8.0.1.orig/src/webargs/__init__.py0000755000000000000000000000061414163275625017011 0ustar00from distutils.version import LooseVersion from marshmallow.utils import missing # Make marshmallow's validation functions importable from webargs from marshmallow import validate from webargs.core import ValidationError from webargs import fields __version__ = "8.0.1" __version_info__ = tuple(LooseVersion(__version__).version) __all__ = ("ValidationError", "fields", "missing", "validate") python-webargs_8.0.1.orig/src/webargs/aiohttpparser.py0000644000000000000000000001374114163275625020141 0ustar00"""aiohttp request argument parsing module. Example: :: import asyncio from aiohttp import web from webargs import fields from webargs.aiohttpparser import use_args hello_args = { 'name': fields.Str(required=True) } @asyncio.coroutine @use_args(hello_args) def index(request, args): return web.Response( body='Hello {}'.format(args['name']).encode('utf-8') ) app = web.Application() app.router.add_route('GET', '/', index) """ import typing from aiohttp import web from aiohttp import web_exceptions from marshmallow import Schema, ValidationError, RAISE from webargs import core from webargs.core import json from webargs.asyncparser import AsyncParser from webargs.multidictproxy import MultiDictProxy def is_json_request(req) -> bool: content_type = req.content_type return core.is_json(content_type) class HTTPUnprocessableEntity(web.HTTPClientError): status_code = 422 # Mapping of status codes to exception classes # Adapted from werkzeug exception_map = {422: HTTPUnprocessableEntity} def _find_exceptions() -> None: for name in web_exceptions.__all__: obj = getattr(web_exceptions, name) try: is_http_exception = issubclass(obj, web_exceptions.HTTPException) except TypeError: is_http_exception = False if not is_http_exception or obj.status_code is None: continue old_obj = exception_map.get(obj.status_code, None) if old_obj is not None and issubclass(obj, old_obj): continue exception_map[obj.status_code] = obj # Collect all exceptions from aiohttp.web_exceptions _find_exceptions() del _find_exceptions class AIOHTTPParser(AsyncParser): """aiohttp request argument parser.""" DEFAULT_UNKNOWN_BY_LOCATION: typing.Dict[str, typing.Optional[str]] = { "match_info": RAISE, "path": RAISE, **core.Parser.DEFAULT_UNKNOWN_BY_LOCATION, } __location_map__ = dict( match_info="load_match_info", path="load_match_info", **core.Parser.__location_map__, ) def load_querystring(self, req, schema: Schema) -> MultiDictProxy: """Return query params from the request as a MultiDictProxy.""" return self._makeproxy(req.query, schema) async def load_form(self, req, schema: Schema) -> MultiDictProxy: """Return form values from the request as a MultiDictProxy.""" post_data = await req.post() return self._makeproxy(post_data, schema) async def load_json_or_form( self, req, schema: Schema ) -> typing.Union[typing.Dict, MultiDictProxy]: data = await self.load_json(req, schema) if data is not core.missing: return data return await self.load_form(req, schema) async def load_json(self, req, schema: Schema): """Return a parsed json payload from the request.""" if not (req.body_exists and is_json_request(req)): return core.missing try: return await req.json(loads=json.loads) except json.JSONDecodeError as exc: if exc.doc == "": return core.missing return self._handle_invalid_json_error(exc, req) except UnicodeDecodeError as exc: return self._handle_invalid_json_error(exc, req) def load_headers(self, req, schema: Schema) -> MultiDictProxy: """Return headers from the request as a MultiDictProxy.""" return self._makeproxy(req.headers, schema) def load_cookies(self, req, schema: Schema) -> MultiDictProxy: """Return cookies from the request as a MultiDictProxy.""" return self._makeproxy(req.cookies, schema) def load_files(self, req, schema: Schema) -> typing.NoReturn: raise NotImplementedError( "load_files is not implemented. You may be able to use load_form for " "parsing upload data." ) def load_match_info(self, req, schema: Schema) -> typing.Mapping: """Load the request's ``match_info``.""" return req.match_info def get_request_from_view_args( self, view: typing.Callable, args: typing.Iterable, kwargs: typing.Mapping ): """Get request object from a handler function or method. Used internally by ``use_args`` and ``use_kwargs``. """ req = None for arg in args: if isinstance(arg, web.Request): req = arg break if isinstance(arg, web.View): req = arg.request break if not isinstance(req, web.Request): raise ValueError("Request argument not found for handler") return req def handle_error( self, error: ValidationError, req, schema: Schema, *, error_status_code: typing.Optional[int], error_headers: typing.Optional[typing.Mapping[str, str]] ) -> typing.NoReturn: """Handle ValidationErrors and return a JSON response of error messages to the client. """ error_class = exception_map.get( error_status_code or self.DEFAULT_VALIDATION_STATUS ) if not error_class: raise LookupError(f"No exception for {error_status_code}") headers = error_headers raise error_class( body=json.dumps(error.messages).encode("utf-8"), headers=headers, content_type="application/json", ) def _handle_invalid_json_error( self, error: typing.Union[json.JSONDecodeError, UnicodeDecodeError], req, *args, **kwargs ) -> typing.NoReturn: error_class = exception_map[400] messages = {"json": ["Invalid JSON body."]} raise error_class( body=json.dumps(messages).encode("utf-8"), content_type="application/json" ) parser = AIOHTTPParser() use_args = parser.use_args # type: typing.Callable use_kwargs = parser.use_kwargs # type: typing.Callable python-webargs_8.0.1.orig/src/webargs/asyncparser.py0000644000000000000000000001717713775063555017622 0ustar00"""Asynchronous request parser.""" import asyncio import functools import inspect import typing from collections.abc import Mapping from marshmallow import Schema, ValidationError import marshmallow as ma from webargs import core AsyncErrorHandler = typing.Callable[..., typing.Awaitable[typing.NoReturn]] class AsyncParser(core.Parser): """Asynchronous variant of `webargs.core.Parser`, where parsing methods may be either coroutines or regular methods. """ # TODO: Lots of duplication from core.Parser here. Rethink. async def parse( self, argmap: core.ArgMap, req: typing.Optional[core.Request] = None, *, location: typing.Optional[str] = None, unknown: typing.Optional[str] = core._UNKNOWN_DEFAULT_PARAM, validate: core.ValidateArg = None, error_status_code: typing.Optional[int] = None, error_headers: typing.Optional[typing.Mapping[str, str]] = None ) -> typing.Optional[typing.Mapping]: """Coroutine variant of `webargs.core.Parser`. Receives the same arguments as `webargs.core.Parser.parse`. """ req = req if req is not None else self.get_default_request() location = location or self.location unknown = ( unknown if unknown != core._UNKNOWN_DEFAULT_PARAM else ( self.unknown if self.unknown != core._UNKNOWN_DEFAULT_PARAM else self.DEFAULT_UNKNOWN_BY_LOCATION.get(location) ) ) load_kwargs: typing.Dict[str, typing.Any] = ( {"unknown": unknown} if unknown else {} ) if req is None: raise ValueError("Must pass req object") data = None validators = core._ensure_list_of_callables(validate) schema = self._get_schema(argmap, req) try: location_data = await self._load_location_data( schema=schema, req=req, location=location ) data = schema.load(location_data, **load_kwargs) self._validate_arguments(data, validators) except ma.exceptions.ValidationError as error: await self._async_on_validation_error( error, req, schema, location, error_status_code=error_status_code, error_headers=error_headers, ) return data async def _load_location_data(self, schema, req, location): """Return a dictionary-like object for the location on the given request. Needs to have the schema in hand in order to correctly handle loading lists from multidict objects and `many=True` schemas. """ loader_func = self._get_loader(location) if asyncio.iscoroutinefunction(loader_func): data = await loader_func(req, schema) else: data = loader_func(req, schema) # when the desired location is empty (no data), provide an empty # dict as the default so that optional arguments in a location # (e.g. optional JSON body) work smoothly if data is core.missing: data = {} return data async def _async_on_validation_error( self, error: ValidationError, req: core.Request, schema: Schema, location: str, *, error_status_code: typing.Optional[int], error_headers: typing.Optional[typing.Mapping[str, str]] ) -> typing.NoReturn: # rewrite messages to be namespaced under the location which created # them # e.g. {"json":{"foo":["Not a valid integer."]}} # instead of # {"foo":["Not a valid integer."]} error.messages = {location: error.messages} error_handler = self.error_callback or self.handle_error # an async error handler was registered, await it if inspect.iscoroutinefunction(error_handler): async_error_handler = typing.cast(AsyncErrorHandler, error_handler) await async_error_handler( error, req, schema, error_status_code=error_status_code, error_headers=error_headers, ) # workaround for mypy not understanding `await Awaitable[NoReturn]` # see: https://github.com/python/mypy/issues/8974 raise NotImplementedError("unreachable") # the error handler was synchronous (e.g. Parser.handle_error) so it # will raise an error else: error_handler( error, req, schema, error_status_code=error_status_code, error_headers=error_headers, ) def use_args( self, argmap: core.ArgMap, req: typing.Optional[core.Request] = None, *, location: str = None, unknown=core._UNKNOWN_DEFAULT_PARAM, as_kwargs: bool = False, validate: core.ValidateArg = None, error_status_code: typing.Optional[int] = None, error_headers: typing.Optional[typing.Mapping[str, str]] = None ) -> typing.Callable[..., typing.Callable]: """Decorator that injects parsed arguments into a view function or method. Receives the same arguments as `webargs.core.Parser.use_args`. """ location = location or self.location request_obj = req # Optimization: If argmap is passed as a dictionary, we only need # to generate a Schema once if isinstance(argmap, Mapping): argmap = self.schema_class.from_dict(argmap)() def decorator(func: typing.Callable) -> typing.Callable: req_ = request_obj if inspect.iscoroutinefunction(func): @functools.wraps(func) async def wrapper(*args, **kwargs): req_obj = req_ if not req_obj: req_obj = self.get_request_from_view_args(func, args, kwargs) # NOTE: At this point, argmap may be a Schema, callable, or dict parsed_args = await self.parse( argmap, req=req_obj, location=location, unknown=unknown, validate=validate, error_status_code=error_status_code, error_headers=error_headers, ) args, kwargs = self._update_args_kwargs( args, kwargs, parsed_args, as_kwargs ) return await func(*args, **kwargs) else: @functools.wraps(func) # type: ignore def wrapper(*args, **kwargs): req_obj = req_ if not req_obj: req_obj = self.get_request_from_view_args(func, args, kwargs) # NOTE: At this point, argmap may be a Schema, callable, or dict parsed_args = yield from self.parse( # type: ignore argmap, req=req_obj, location=location, unknown=unknown, validate=validate, error_status_code=error_status_code, error_headers=error_headers, ) args, kwargs = self._update_args_kwargs( args, kwargs, parsed_args, as_kwargs ) return func(*args, **kwargs) return wrapper return decorator python-webargs_8.0.1.orig/src/webargs/bottleparser.py0000644000000000000000000000575314163275625017766 0ustar00"""Bottle request argument parsing module. Example: :: from bottle import route, run from marshmallow import fields from webargs.bottleparser import use_args hello_args = { 'name': fields.Str(missing='World') } @route('/', method='GET', apply=use_args(hello_args)) def index(args): return 'Hello ' + args['name'] if __name__ == '__main__': run(debug=True) """ import bottle from webargs import core class BottleParser(core.Parser): """Bottle.py request argument parser.""" def _handle_invalid_json_error(self, error, req, *args, **kwargs): raise bottle.HTTPError( status=400, body={"json": ["Invalid JSON body."]}, exception=error ) def _raw_load_json(self, req): """Read a json payload from the request.""" try: data = req.json except AttributeError: return core.missing # unfortunately, bottle does not distinguish between an emtpy body, "", # and a body containing the valid JSON value null, "null" # so these can't be properly disambiguated # as our best-effort solution, treat None as missing and ignore the # (admittedly unusual) "null" case # see: https://github.com/bottlepy/bottle/issues/1160 if data is None: return core.missing return data def load_querystring(self, req, schema): """Return query params from the request as a MultiDictProxy.""" return self._makeproxy(req.query, schema) def load_form(self, req, schema): """Return form values from the request as a MultiDictProxy.""" # For consistency with other parsers' behavior, don't attempt to # parse if content-type is mismatched. # TODO: Make this check more specific if core.is_json(req.content_type): return core.missing return self._makeproxy(req.forms, schema) def load_headers(self, req, schema): """Return headers from the request as a MultiDictProxy.""" return self._makeproxy(req.headers, schema) def load_cookies(self, req, schema): """Return cookies from the request.""" return req.cookies def load_files(self, req, schema): """Return files from the request as a MultiDictProxy.""" return self._makeproxy(req.files, schema) def handle_error(self, error, req, schema, *, error_status_code, error_headers): """Handles errors during parsing. Aborts the current request with a 400 error. """ status_code = error_status_code or self.DEFAULT_VALIDATION_STATUS raise bottle.HTTPError( status=status_code, body=error.messages, headers=error_headers, exception=error, ) def get_default_request(self): """Override to use bottle's thread-local request object by default.""" return bottle.request parser = BottleParser() use_args = parser.use_args use_kwargs = parser.use_kwargs python-webargs_8.0.1.orig/src/webargs/core.py0000644000000000000000000005631114163275625016204 0ustar00import functools import typing import logging from collections.abc import Mapping import json import marshmallow as ma from marshmallow import ValidationError from marshmallow.utils import missing from webargs.multidictproxy import MultiDictProxy logger = logging.getLogger(__name__) __all__ = [ "ValidationError", "Parser", "missing", "parse_json", ] Request = typing.TypeVar("Request") ArgMap = typing.Union[ ma.Schema, typing.Mapping[str, ma.fields.Field], typing.Callable[[Request], ma.Schema], ] ValidateArg = typing.Union[None, typing.Callable, typing.Iterable[typing.Callable]] CallableList = typing.List[typing.Callable] ErrorHandler = typing.Callable[..., typing.NoReturn] # generic type var with no particular meaning T = typing.TypeVar("T") # a value used as the default for arguments, so that when `None` is passed, it # can be distinguished from the default value _UNKNOWN_DEFAULT_PARAM = "_default" DEFAULT_VALIDATION_STATUS: int = 422 def _iscallable(x) -> bool: # workaround for # https://github.com/python/mypy/issues/9778 return callable(x) def _callable_or_raise(obj: typing.Optional[T]) -> typing.Optional[T]: """Makes sure an object is callable if it is not ``None``. If not callable, a ValueError is raised. """ if obj and not _iscallable(obj): raise ValueError(f"{obj!r} is not callable.") return obj def get_mimetype(content_type: str) -> str: return content_type.split(";")[0].strip() # Adapted from werkzeug: # https://github.com/mitsuhiko/werkzeug/blob/master/werkzeug/wrappers.py def is_json(mimetype: typing.Optional[str]) -> bool: """Indicates if this mimetype is JSON or not. By default a request is considered to include JSON data if the mimetype is ``application/json`` or ``application/*+json``. """ if not mimetype: return False if ";" in mimetype: # Allow Content-Type header to be passed mimetype = get_mimetype(mimetype) if mimetype == "application/json": return True if mimetype.startswith("application/") and mimetype.endswith("+json"): return True return False def parse_json(s: typing.AnyStr, *, encoding: str = "utf-8") -> typing.Any: if isinstance(s, str): decoded = s else: try: decoded = s.decode(encoding) except UnicodeDecodeError as exc: raise json.JSONDecodeError( f"Bytes decoding error : {exc.reason}", doc=str(exc.object), pos=exc.start, ) return json.loads(decoded) def _ensure_list_of_callables(obj: typing.Any) -> CallableList: if obj: if isinstance(obj, (list, tuple)): validators = typing.cast(CallableList, list(obj)) elif callable(obj): validators = [obj] else: raise ValueError(f"{obj!r} is not a callable or list of callables.") else: validators = [] return validators class Parser: """Base parser class that provides high-level implementation for parsing a request. Descendant classes must provide lower-level implementations for reading data from different locations, e.g. ``load_json``, ``load_querystring``, etc. :param str location: Default location to use for data :param str unknown: A default value to pass for ``unknown`` when calling the schema's ``load`` method. Defaults to EXCLUDE for non-body locations and RAISE for request bodies. Pass ``None`` to use the schema's setting instead. :param callable error_handler: Custom error handler function. """ #: Default location to check for data DEFAULT_LOCATION: str = "json" #: Default value to use for 'unknown' on schema load # on a per-location basis DEFAULT_UNKNOWN_BY_LOCATION: typing.Dict[str, typing.Optional[str]] = { "json": None, "form": None, "json_or_form": None, "querystring": ma.EXCLUDE, "query": ma.EXCLUDE, "headers": ma.EXCLUDE, "cookies": ma.EXCLUDE, "files": ma.EXCLUDE, } #: The marshmallow Schema class to use when creating new schemas DEFAULT_SCHEMA_CLASS: typing.Type = ma.Schema #: Default status code to return for validation errors DEFAULT_VALIDATION_STATUS: int = DEFAULT_VALIDATION_STATUS #: Default error message for validation errors DEFAULT_VALIDATION_MESSAGE: str = "Invalid value." #: field types which should always be treated as if they set `is_multiple=True` KNOWN_MULTI_FIELDS: typing.List[typing.Type] = [ma.fields.List, ma.fields.Tuple] #: Maps location => method name __location_map__: typing.Dict[str, typing.Union[str, typing.Callable]] = { "json": "load_json", "querystring": "load_querystring", "query": "load_querystring", "form": "load_form", "headers": "load_headers", "cookies": "load_cookies", "files": "load_files", "json_or_form": "load_json_or_form", } def __init__( self, location: typing.Optional[str] = None, *, unknown: typing.Optional[str] = _UNKNOWN_DEFAULT_PARAM, error_handler: typing.Optional[ErrorHandler] = None, schema_class: typing.Optional[typing.Type] = None ): self.location = location or self.DEFAULT_LOCATION self.error_callback: typing.Optional[ErrorHandler] = _callable_or_raise( error_handler ) self.schema_class = schema_class or self.DEFAULT_SCHEMA_CLASS self.unknown = unknown def _makeproxy( self, multidict, schema: ma.Schema, cls: typing.Type = MultiDictProxy ): """Create a multidict proxy object with options from the current parser""" return cls(multidict, schema, known_multi_fields=tuple(self.KNOWN_MULTI_FIELDS)) def _get_loader(self, location: str) -> typing.Callable: """Get the loader function for the given location. :raises: ValueError if a given location is invalid. """ valid_locations = set(self.__location_map__.keys()) if location not in valid_locations: raise ValueError(f"Invalid location argument: {location}") # Parsing function to call # May be a method name (str) or a function func = self.__location_map__[location] if isinstance(func, str): return getattr(self, func) return func def _load_location_data( self, *, schema: ma.Schema, req: Request, location: str ) -> typing.Mapping: """Return a dictionary-like object for the location on the given request. Needs to have the schema in hand in order to correctly handle loading lists from multidict objects and `many=True` schemas. """ loader_func = self._get_loader(location) data = loader_func(req, schema) # when the desired location is empty (no data), provide an empty # dict as the default so that optional arguments in a location # (e.g. optional JSON body) work smoothly if data is missing: data = {} return data def _on_validation_error( self, error: ValidationError, req: Request, schema: ma.Schema, location: str, *, error_status_code: typing.Optional[int], error_headers: typing.Optional[typing.Mapping[str, str]] ) -> typing.NoReturn: # rewrite messages to be namespaced under the location which created # them # e.g. {"json":{"foo":["Not a valid integer."]}} # instead of # {"foo":["Not a valid integer."]} error.messages = {location: error.messages} error_handler: ErrorHandler = self.error_callback or self.handle_error error_handler( error, req, schema, error_status_code=error_status_code, error_headers=error_headers, ) def _validate_arguments(self, data: typing.Any, validators: CallableList) -> None: # although `data` is typically a Mapping, nothing forbids a `schema.load` # from returning an arbitrary object subject to validators for validator in validators: if validator(data) is False: msg = self.DEFAULT_VALIDATION_MESSAGE raise ValidationError(msg, data=data) def _get_schema(self, argmap: ArgMap, req: Request) -> ma.Schema: """Return a `marshmallow.Schema` for the given argmap and request. :param argmap: Either a `marshmallow.Schema`, `dict` of argname -> `marshmallow.fields.Field` pairs, or a callable that returns a `marshmallow.Schema` instance. :param req: The request object being parsed. :rtype: marshmallow.Schema """ if isinstance(argmap, ma.Schema): schema = argmap elif isinstance(argmap, type) and issubclass(argmap, ma.Schema): schema = argmap() elif callable(argmap): schema = argmap(req) else: schema = self.schema_class.from_dict(argmap)() return schema def parse( self, argmap: ArgMap, req: typing.Optional[Request] = None, *, location: typing.Optional[str] = None, unknown: typing.Optional[str] = _UNKNOWN_DEFAULT_PARAM, validate: ValidateArg = None, error_status_code: typing.Optional[int] = None, error_headers: typing.Optional[typing.Mapping[str, str]] = None ): """Main request parsing method. :param argmap: Either a `marshmallow.Schema`, a `dict` of argname -> `marshmallow.fields.Field` pairs, or a callable which accepts a request and returns a `marshmallow.Schema`. :param req: The request object to parse. :param str location: Where on the request to load values. Can be any of the values in :py:attr:`~__location_map__`. By default, that means one of ``('json', 'query', 'querystring', 'form', 'headers', 'cookies', 'files', 'json_or_form')``. :param str unknown: A value to pass for ``unknown`` when calling the schema's ``load`` method. Defaults to EXCLUDE for non-body locations and RAISE for request bodies. Pass ``None`` to use the schema's setting instead. :param callable validate: Validation function or list of validation functions that receives the dictionary of parsed arguments. Validator either returns a boolean or raises a :exc:`ValidationError`. :param int error_status_code: Status code passed to error handler functions when a `ValidationError` is raised. :param dict error_headers: Headers passed to error handler functions when a a `ValidationError` is raised. :return: A dictionary of parsed arguments """ req = req if req is not None else self.get_default_request() location = location or self.location # precedence order: explicit, instance setting, default per location unknown = ( unknown if unknown != _UNKNOWN_DEFAULT_PARAM else ( self.unknown if self.unknown != _UNKNOWN_DEFAULT_PARAM else self.DEFAULT_UNKNOWN_BY_LOCATION.get(location) ) ) load_kwargs: typing.Dict[str, typing.Any] = ( {"unknown": unknown} if unknown else {} ) if req is None: raise ValueError("Must pass req object") data = None validators = _ensure_list_of_callables(validate) schema = self._get_schema(argmap, req) try: location_data = self._load_location_data( schema=schema, req=req, location=location ) preprocessed_data = self.pre_load( location_data, schema=schema, req=req, location=location ) data = schema.load(preprocessed_data, **load_kwargs) self._validate_arguments(data, validators) except ma.exceptions.ValidationError as error: self._on_validation_error( error, req, schema, location, error_status_code=error_status_code, error_headers=error_headers, ) raise ValueError( "_on_validation_error hook did not raise an exception" ) from error return data def get_default_request(self) -> typing.Optional[Request]: """Optional override. Provides a hook for frameworks that use thread-local request objects. """ return None def get_request_from_view_args( self, view: typing.Callable, args: typing.Tuple, kwargs: typing.Mapping[str, typing.Any], ) -> typing.Optional[Request]: """Optional override. Returns the request object to be parsed, given a view function's args and kwargs. Used by the `use_args` and `use_kwargs` to get a request object from a view's arguments. :param callable view: The view function or method being decorated by `use_args` or `use_kwargs` :param tuple args: Positional arguments passed to ``view``. :param dict kwargs: Keyword arguments passed to ``view``. """ return None @staticmethod def _update_args_kwargs( args: typing.Tuple, kwargs: typing.Dict[str, typing.Any], parsed_args: typing.Tuple, as_kwargs: bool, ) -> typing.Tuple[typing.Tuple, typing.Mapping]: """Update args or kwargs with parsed_args depending on as_kwargs""" if as_kwargs: kwargs.update(parsed_args) else: # Add parsed_args after other positional arguments args += (parsed_args,) return args, kwargs def use_args( self, argmap: ArgMap, req: typing.Optional[Request] = None, *, location: typing.Optional[str] = None, unknown: typing.Optional[str] = _UNKNOWN_DEFAULT_PARAM, as_kwargs: bool = False, validate: ValidateArg = None, error_status_code: typing.Optional[int] = None, error_headers: typing.Optional[typing.Mapping[str, str]] = None ) -> typing.Callable[..., typing.Callable]: """Decorator that injects parsed arguments into a view function or method. Example usage with Flask: :: @app.route('/echo', methods=['get', 'post']) @parser.use_args({'name': fields.Str()}, location="querystring") def greet(args): return 'Hello ' + args['name'] :param argmap: Either a `marshmallow.Schema`, a `dict` of argname -> `marshmallow.fields.Field` pairs, or a callable which accepts a request and returns a `marshmallow.Schema`. :param str location: Where on the request to load values. :param str unknown: A value to pass for ``unknown`` when calling the schema's ``load`` method. :param bool as_kwargs: Whether to insert arguments as keyword arguments. :param callable validate: Validation function that receives the dictionary of parsed arguments. If the function returns ``False``, the parser will raise a :exc:`ValidationError`. :param int error_status_code: Status code passed to error handler functions when a `ValidationError` is raised. :param dict error_headers: Headers passed to error handler functions when a a `ValidationError` is raised. """ location = location or self.location request_obj = req # Optimization: If argmap is passed as a dictionary, we only need # to generate a Schema once if isinstance(argmap, Mapping): argmap = self.schema_class.from_dict(argmap)() def decorator(func): req_ = request_obj @functools.wraps(func) def wrapper(*args, **kwargs): req_obj = req_ if not req_obj: req_obj = self.get_request_from_view_args(func, args, kwargs) # NOTE: At this point, argmap may be a Schema, or a callable parsed_args = self.parse( argmap, req=req_obj, location=location, unknown=unknown, validate=validate, error_status_code=error_status_code, error_headers=error_headers, ) args, kwargs = self._update_args_kwargs( args, kwargs, parsed_args, as_kwargs ) return func(*args, **kwargs) wrapper.__wrapped__ = func return wrapper return decorator def use_kwargs(self, *args, **kwargs) -> typing.Callable: """Decorator that injects parsed arguments into a view function or method as keyword arguments. This is a shortcut to :meth:`use_args` with ``as_kwargs=True``. Example usage with Flask: :: @app.route('/echo', methods=['get', 'post']) @parser.use_kwargs({'name': fields.Str()}) def greet(name): return 'Hello ' + name Receives the same ``args`` and ``kwargs`` as :meth:`use_args`. """ kwargs["as_kwargs"] = True return self.use_args(*args, **kwargs) def location_loader(self, name: str): """Decorator that registers a function for loading a request location. The wrapped function receives a schema and a request. The schema will usually not be relevant, but it's important in some cases -- most notably in order to correctly load multidict values into list fields. Without the schema, there would be no way to know whether to simply `.get()` or `.getall()` from a multidict for a given value. Example: :: from webargs import core parser = core.Parser() @parser.location_loader("name") def load_data(request, schema): return request.data :param str name: The name of the location to register. """ def decorator(func): self.__location_map__[name] = func return func return decorator def error_handler(self, func: ErrorHandler) -> ErrorHandler: """Decorator that registers a custom error handling function. The function should receive the raised error, request object, `marshmallow.Schema` instance used to parse the request, error status code, and headers to use for the error response. Overrides the parser's ``handle_error`` method. Example: :: from webargs import flaskparser parser = flaskparser.FlaskParser() class CustomError(Exception): pass @parser.error_handler def handle_error(error, req, schema, *, error_status_code, error_headers): raise CustomError(error.messages) :param callable func: The error callback to register. """ self.error_callback = func return func def pre_load( self, location_data: Mapping, *, schema: ma.Schema, req: Request, location: str ) -> Mapping: """A method of the parser which can transform data after location loading is done. By default it does nothing, but users can subclass parsers and override this method. """ return location_data def _handle_invalid_json_error( self, error: typing.Union[json.JSONDecodeError, UnicodeDecodeError], req: Request, *args, **kwargs ) -> typing.NoReturn: """Internal hook for overriding treatment of JSONDecodeErrors. Invoked by default `load_json` implementation. External parsers can just implement their own behavior for load_json , so this is not part of the public parser API. """ raise error def load_json(self, req: Request, schema: ma.Schema) -> typing.Any: """Load JSON from a request object or return `missing` if no value can be found. """ # NOTE: although this implementation is real/concrete and used by # several of the parsers in webargs, it relies on the internal hooks # `_handle_invalid_json_error` and `_raw_load_json` # these methods are not part of the public API and are used to simplify # code sharing amongst the built-in webargs parsers try: return self._raw_load_json(req) except json.JSONDecodeError as exc: if exc.doc == "": return missing return self._handle_invalid_json_error(exc, req) except UnicodeDecodeError as exc: return self._handle_invalid_json_error(exc, req) def load_json_or_form(self, req: Request, schema: ma.Schema): """Load data from a request, accepting either JSON or form-encoded data. The data will first be loaded as JSON, and, if that fails, it will be loaded as a form post. """ data = self.load_json(req, schema) if data is not missing: return data return self.load_form(req, schema) # Abstract Methods def _raw_load_json(self, req: Request): """Internal hook method for implementing load_json() Get a request body for feeding in to `load_json`, and parse it either using core.parse_json() or similar utilities which raise JSONDecodeErrors. Ensure consistent behavior when encountering decoding errors. The default implementation here simply returns `missing`, and the default implementation of `load_json` above will pass that value through. However, by implementing a "mostly concrete" version of load_json with this as a hook for getting data, we consolidate the logic for handling those JSONDecodeErrors. """ return missing def load_querystring(self, req: Request, schema: ma.Schema): """Load the query string of a request object or return `missing` if no value can be found. """ return missing def load_form(self, req: Request, schema: ma.Schema): """Load the form data of a request object or return `missing` if no value can be found. """ return missing def load_headers(self, req: Request, schema: ma.Schema): """Load the headers or return `missing` if no value can be found.""" return missing def load_cookies(self, req: Request, schema: ma.Schema): """Load the cookies from the request or return `missing` if no value can be found. """ return missing def load_files(self, req: Request, schema: ma.Schema): """Load files from the request or return `missing` if no values can be found. """ return missing def handle_error( self, error: ValidationError, req: Request, schema: ma.Schema, *, error_status_code: int, error_headers: typing.Mapping[str, str] ) -> typing.NoReturn: """Called if an error occurs while parsing args. By default, just logs and raises ``error``. """ logger.error(error) raise error python-webargs_8.0.1.orig/src/webargs/djangoparser.py0000644000000000000000000000466414163275625017737 0ustar00"""Django request argument parsing. Example usage: :: from django.views.generic import View from django.http import HttpResponse from marshmallow import fields from webargs.djangoparser import use_args hello_args = { 'name': fields.Str(missing='World') } class MyView(View): @use_args(hello_args) def get(self, args, request): return HttpResponse('Hello ' + args['name']) """ from webargs import core def is_json_request(req): return core.is_json(req.content_type) class DjangoParser(core.Parser): """Django request argument parser. .. warning:: :class:`DjangoParser` does not override :meth:`handle_error `, so your Django views are responsible for catching any :exc:`ValidationErrors` raised by the parser and returning the appropriate `HTTPResponse`. """ def _raw_load_json(self, req): """Read a json payload from the request for the core parser's load_json Checks the input mimetype and may return 'missing' if the mimetype is non-json, even if the request body is parseable as json.""" if not is_json_request(req): return core.missing return core.parse_json(req.body) def load_querystring(self, req, schema): """Return query params from the request as a MultiDictProxy.""" return self._makeproxy(req.GET, schema) def load_form(self, req, schema): """Return form values from the request as a MultiDictProxy.""" return self._makeproxy(req.POST, schema) def load_cookies(self, req, schema): """Return cookies from the request.""" return req.COOKIES def load_headers(self, req, schema): """Return headers from the request.""" # Django's HttpRequest.headers is a case-insensitive dict type, but it # isn't a multidict, so this is not proxied return req.headers def load_files(self, req, schema): """Return files from the request as a MultiDictProxy.""" return self._makeproxy(req.FILES, schema) def get_request_from_view_args(self, view, args, kwargs): # The first argument is either `self` or `request` try: # self.request return args[0].request except AttributeError: # first arg is request return args[0] parser = DjangoParser() use_args = parser.use_args use_kwargs = parser.use_kwargs python-webargs_8.0.1.orig/src/webargs/falconparser.py0000644000000000000000000001467014163275625017735 0ustar00"""Falcon request argument parsing module. """ import falcon from falcon.util.uri import parse_query_string import marshmallow as ma from webargs import core HTTP_422 = "422 Unprocessable Entity" # Mapping of int status codes to string status status_map = {422: HTTP_422} # Collect all exceptions from falcon.status_codes def _find_exceptions(): for name in filter(lambda n: n.startswith("HTTP"), dir(falcon.status_codes)): status = getattr(falcon.status_codes, name) status_code = int(status.split(" ")[0]) status_map[status_code] = status _find_exceptions() del _find_exceptions def is_json_request(req): content_type = req.get_header("Content-Type") return content_type and core.is_json(content_type) # NOTE: Adapted from falcon.request.Request._parse_form_urlencoded def parse_form_body(req): if ( req.content_type is not None and "application/x-www-form-urlencoded" in req.content_type ): body = req.stream.read(req.content_length or 0) try: body = body.decode("ascii") except UnicodeDecodeError: body = None req.log_error( "Non-ASCII characters found in form body " "with Content-Type of " "application/x-www-form-urlencoded. Body " "will be ignored." ) if body: return parse_query_string(body, keep_blank=req.options.keep_blank_qs_values) return core.missing class HTTPError(falcon.HTTPError): """HTTPError that stores a dictionary of validation error messages.""" def __init__(self, status, errors, *args, **kwargs): self.errors = errors super().__init__(status, *args, **kwargs) def to_dict(self, *args, **kwargs): """Override `falcon.HTTPError` to include error messages in responses.""" ret = super().to_dict(*args, **kwargs) if self.errors is not None: ret["errors"] = self.errors return ret class FalconParser(core.Parser): """Falcon request argument parser. Defaults to using the `media` location. See :py:meth:`~FalconParser.load_media` for details on the media location.""" # by default, Falcon will use the 'media' location to load data # # this effectively looks the same as loading JSON data by default, but if # you add a handler for a different media type to Falcon, webargs will # automatically pick up on that capability DEFAULT_LOCATION = "media" DEFAULT_UNKNOWN_BY_LOCATION = dict( media=ma.RAISE, **core.Parser.DEFAULT_UNKNOWN_BY_LOCATION ) __location_map__ = dict(media="load_media", **core.Parser.__location_map__) # Note on the use of MultiDictProxy throughout: # Falcon parses query strings and form values into ordinary dicts, but with # the values listified where appropriate # it is still therefore necessary in these cases to wrap them in # MultiDictProxy because we need to use the schema to determine when single # values should be wrapped in lists due to the type of the destination # field def load_querystring(self, req, schema): """Return query params from the request as a MultiDictProxy.""" return self._makeproxy(req.params, schema) def load_form(self, req, schema): """Return form values from the request as a MultiDictProxy .. note:: The request stream will be read and left at EOF. """ form = parse_form_body(req) if form is core.missing: return form return self._makeproxy(form, schema) def load_media(self, req, schema): """Return data unpacked and parsed by one of Falcon's media handlers. By default, Falcon only handles JSON payloads. To configure additional media handlers, see the `Falcon documentation on media types`__. .. _FalconMedia: https://falcon.readthedocs.io/en/stable/api/media.html __ FalconMedia_ .. note:: The request stream will be read and left at EOF. """ # if there is no body, return missing instead of erroring if req.content_length in (None, 0): return core.missing return req.media def _raw_load_json(self, req): """Return a json payload from the request for the core parser's load_json Checks the input mimetype and may return 'missing' if the mimetype is non-json, even if the request body is parseable as json.""" if not is_json_request(req) or req.content_length in (None, 0): return core.missing body = req.stream.read(req.content_length) if body: return core.parse_json(body) return core.missing def load_headers(self, req, schema): """Return headers from the request.""" # Falcon only exposes headers as a dict (not multidict) return req.headers def load_cookies(self, req, schema): """Return cookies from the request.""" # Cookies are expressed in Falcon as a dict, but the possibility of # multiple values for a cookie is preserved internally -- if desired in # the future, webargs could add a MultiDict type for Cookies here built # from (req, schema), but Falcon does not provide one out of the box return req.cookies def get_request_from_view_args(self, view, args, kwargs): """Get request from a resource method's arguments. Assumes that request is the second argument. """ req = args[1] if not isinstance(req, falcon.Request): raise TypeError("Argument is not a falcon.Request") return req def load_files(self, req, schema): raise NotImplementedError( f"Parsing files not yet supported by {self.__class__.__name__}" ) def handle_error(self, error, req, schema, *, error_status_code, error_headers): """Handles errors during parsing.""" status = status_map.get(error_status_code or self.DEFAULT_VALIDATION_STATUS) if status is None: raise LookupError(f"Status code {error_status_code} not supported") raise HTTPError(status, errors=error.messages, headers=error_headers) def _handle_invalid_json_error(self, error, req, *args, **kwargs): status = status_map[400] messages = {"json": ["Invalid JSON body."]} raise HTTPError(status, errors=messages) parser = FalconParser() use_args = parser.use_args use_kwargs = parser.use_kwargs python-webargs_8.0.1.orig/src/webargs/fields.py0000644000000000000000000000772314163275625016525 0ustar00"""Field classes. Includes all fields from `marshmallow.fields` in addition to a custom `Nested` field and `DelimitedList`. All fields can optionally take a special `location` keyword argument, which tells webargs where to parse the request argument from. .. code-block:: python args = { "active": fields.Bool(location="query"), "content_type": fields.Str(data_key="Content-Type", location="headers"), } """ import typing import marshmallow as ma # Expose all fields from marshmallow.fields. from marshmallow.fields import * # noqa: F40 __all__ = ["DelimitedList"] + ma.fields.__all__ class Nested(ma.fields.Nested): # type: ignore[no-redef] """Same as `marshmallow.fields.Nested`, except can be passed a dictionary as the first argument, which will be converted to a `marshmallow.Schema`. .. note:: The schema class here will always be `marshmallow.Schema`, regardless of whether a custom schema class is set on the parser. Pass an explicit schema class if necessary. """ def __init__(self, nested, *args, **kwargs): if isinstance(nested, dict): nested = ma.Schema.from_dict(nested) super().__init__(nested, *args, **kwargs) class DelimitedFieldMixin: """ This is a mixin class for subclasses of ma.fields.List and ma.fields.Tuple which split on a pre-specified delimiter. By default, the delimiter will be "," Because we want the MRO to reach this class before the List or Tuple class, it must be listed first in the superclasses For example, a DelimitedList-like type can be defined like so: >>> class MyDelimitedList(DelimitedFieldMixin, ma.fields.List): >>> pass """ delimiter: str = "," # delimited fields set is_multiple=False for webargs.core.is_multiple is_multiple: bool = False def _serialize(self, value, attr, obj, **kwargs): # serializing will start with parent-class serialization, so that we correctly # output lists of non-primitive types, e.g. DelimitedList(DateTime) return self.delimiter.join( format(each) for each in super()._serialize(value, attr, obj, **kwargs) ) def _deserialize(self, value, attr, data, **kwargs): # attempting to deserialize from a non-string source is an error if not isinstance(value, (str, bytes)): raise self.make_error("invalid") values = value.split(self.delimiter) if value else [] return super()._deserialize(values, attr, data, **kwargs) class DelimitedList(DelimitedFieldMixin, ma.fields.List): """A field which is similar to a List, but takes its input as a delimited string (e.g. "foo,bar,baz"). Like List, it can be given a nested field type which it will use to de/serialize each element of the list. :param Field cls_or_instance: A field class or instance. :param str delimiter: Delimiter between values. """ default_error_messages = {"invalid": "Not a valid delimited list."} def __init__( self, cls_or_instance: typing.Union[ma.fields.Field, type], *, delimiter: typing.Optional[str] = None, **kwargs ): self.delimiter = delimiter or self.delimiter super().__init__(cls_or_instance, **kwargs) class DelimitedTuple(DelimitedFieldMixin, ma.fields.Tuple): """A field which is similar to a Tuple, but takes its input as a delimited string (e.g. "foo,bar,baz"). Like Tuple, it can be given a tuple of nested field types which it will use to de/serialize each element of the tuple. :param Iterable[Field] tuple_fields: An iterable of field classes or instances. :param str delimiter: Delimiter between values. """ default_error_messages = {"invalid": "Not a valid delimited tuple."} def __init__( self, tuple_fields, *, delimiter: typing.Optional[str] = None, **kwargs ): self.delimiter = delimiter or self.delimiter super().__init__(tuple_fields, **kwargs) python-webargs_8.0.1.orig/src/webargs/flaskparser.py0000644000000000000000000000710114163275625017562 0ustar00"""Flask request argument parsing module. Example: :: from flask import Flask from webargs import fields from webargs.flaskparser import use_args app = Flask(__name__) user_detail_args = { 'per_page': fields.Int() } @app.route("/user/") @use_args(user_detail_args) def user_detail(args, uid): return ("The user page for user {uid}, showing {per_page} posts.").format( uid=uid, per_page=args["per_page"] ) """ import typing import flask from werkzeug.exceptions import HTTPException import marshmallow as ma from webargs import core def abort(http_status_code, exc=None, **kwargs): """Raise a HTTPException for the given http_status_code. Attach any keyword arguments to the exception for later processing. From Flask-Restful. See NOTICE file for license information. """ try: flask.abort(http_status_code) except HTTPException as err: err.data = kwargs err.exc = exc raise err def is_json_request(req): return core.is_json(req.mimetype) class FlaskParser(core.Parser): """Flask request argument parser.""" DEFAULT_UNKNOWN_BY_LOCATION: typing.Dict[str, typing.Optional[str]] = { "view_args": ma.RAISE, "path": ma.RAISE, **core.Parser.DEFAULT_UNKNOWN_BY_LOCATION, } __location_map__ = dict( view_args="load_view_args", path="load_view_args", **core.Parser.__location_map__, ) def _raw_load_json(self, req): """Return a json payload from the request for the core parser's load_json Checks the input mimetype and may return 'missing' if the mimetype is non-json, even if the request body is parseable as json.""" if not is_json_request(req): return core.missing return core.parse_json(req.get_data(cache=True)) def _handle_invalid_json_error(self, error, req, *args, **kwargs): abort(400, exc=error, messages={"json": ["Invalid JSON body."]}) def load_view_args(self, req, schema): """Return the request's ``view_args`` or ``missing`` if there are none.""" return req.view_args or core.missing def load_querystring(self, req, schema): """Return query params from the request as a MultiDictProxy.""" return self._makeproxy(req.args, schema) def load_form(self, req, schema): """Return form values from the request as a MultiDictProxy.""" return self._makeproxy(req.form, schema) def load_headers(self, req, schema): """Return headers from the request as a MultiDictProxy.""" return self._makeproxy(req.headers, schema) def load_cookies(self, req, schema): """Return cookies from the request.""" return req.cookies def load_files(self, req, schema): """Return files from the request as a MultiDictProxy.""" return self._makeproxy(req.files, schema) def handle_error(self, error, req, schema, *, error_status_code, error_headers): """Handles errors during parsing. Aborts the current HTTP request and responds with a 422 error. """ status_code = error_status_code or self.DEFAULT_VALIDATION_STATUS abort( status_code, exc=error, messages=error.messages, schema=schema, headers=error_headers, ) def get_default_request(self): """Override to use Flask's thread-local request object by default""" return flask.request parser = FlaskParser() use_args = parser.use_args use_kwargs = parser.use_kwargs python-webargs_8.0.1.orig/src/webargs/multidictproxy.py0000644000000000000000000000601014163275625020343 0ustar00from collections.abc import Mapping import typing import marshmallow as ma class MultiDictProxy(Mapping): """ A proxy object which wraps multidict types along with a matching schema Whenever a value is looked up, it is checked against the schema to see if there is a matching field where `is_multiple` is True. If there is, then the data should be loaded as a list or tuple. In all other cases, __getitem__ proxies directly to the input multidict. """ def __init__( self, multidict, schema: ma.Schema, known_multi_fields: typing.Tuple[typing.Type, ...] = ( ma.fields.List, ma.fields.Tuple, ), ): self.data = multidict self.known_multi_fields = known_multi_fields self.multiple_keys = self._collect_multiple_keys(schema) def _is_multiple(self, field: ma.fields.Field) -> bool: """Return whether or not `field` handles repeated/multi-value arguments.""" # fields which set `is_multiple = True/False` will have the value selected, # otherwise, we check for explicit criteria is_multiple_attr = getattr(field, "is_multiple", None) if is_multiple_attr is not None: return is_multiple_attr return isinstance(field, self.known_multi_fields) def _collect_multiple_keys(self, schema: ma.Schema): result = set() for name, field in schema.fields.items(): if not self._is_multiple(field): continue result.add(field.data_key if field.data_key is not None else name) return result def __getitem__(self, key): val = self.data.get(key, ma.missing) if val is ma.missing or key not in self.multiple_keys: return val if hasattr(self.data, "getlist"): return self.data.getlist(key) if hasattr(self.data, "getall"): return self.data.getall(key) if isinstance(val, (list, tuple)): return val if val is None: return None return [val] def __str__(self): # str(proxy) proxies to str(proxy.data) return str(self.data) def __repr__(self): return "MultiDictProxy(data={!r}, multiple_keys={!r})".format( self.data, self.multiple_keys ) def __delitem__(self, key): del self.data[key] def __setitem__(self, key, value): self.data[key] = value def __getattr__(self, name): return getattr(self.data, name) def __iter__(self): for x in iter(self.data): # special case for header dicts which produce an iterator of tuples # instead of an iterator of strings if isinstance(x, tuple): yield x[0] else: yield x def __contains__(self, x): return x in self.data def __len__(self): return len(self.data) def __eq__(self, other): return self.data == other def __ne__(self, other): return self.data != other python-webargs_8.0.1.orig/src/webargs/py.typed0000644000000000000000000000000013775063555016366 0ustar00python-webargs_8.0.1.orig/src/webargs/pyramidparser.py0000644000000000000000000001525714163275625020142 0ustar00"""Pyramid request argument parsing. Example usage: :: from wsgiref.simple_server import make_server from pyramid.config import Configurator from pyramid.response import Response from marshmallow import fields from webargs.pyramidparser import use_args hello_args = { 'name': fields.Str(missing='World') } @use_args(hello_args) def hello_world(request, args): return Response('Hello ' + args['name']) if __name__ == '__main__': config = Configurator() config.add_route('hello', '/') config.add_view(hello_world, route_name='hello') app = config.make_wsgi_app() server = make_server('0.0.0.0', 6543, app) server.serve_forever() """ import functools import typing from collections.abc import Mapping from webob.multidict import MultiDict from pyramid.httpexceptions import exception_response import marshmallow as ma from webargs import core from webargs.core import json def is_json_request(req): return core.is_json(req.headers.get("content-type")) class PyramidParser(core.Parser): """Pyramid request argument parser.""" DEFAULT_UNKNOWN_BY_LOCATION: typing.Dict[str, typing.Optional[str]] = { "matchdict": ma.RAISE, "path": ma.RAISE, **core.Parser.DEFAULT_UNKNOWN_BY_LOCATION, } __location_map__ = dict( matchdict="load_matchdict", path="load_matchdict", **core.Parser.__location_map__, ) def _raw_load_json(self, req): """Return a json payload from the request for the core parser's load_json Checks the input mimetype and may return 'missing' if the mimetype is non-json, even if the request body is parseable as json.""" if not is_json_request(req): return core.missing return core.parse_json(req.body, encoding=req.charset) def load_querystring(self, req, schema): """Return query params from the request as a MultiDictProxy.""" return self._makeproxy(req.GET, schema) def load_form(self, req, schema): """Return form values from the request as a MultiDictProxy.""" return self._makeproxy(req.POST, schema) def load_cookies(self, req, schema): """Return cookies from the request as a MultiDictProxy.""" return self._makeproxy(req.cookies, schema) def load_headers(self, req, schema): """Return headers from the request as a MultiDictProxy.""" return self._makeproxy(req.headers, schema) def load_files(self, req, schema): """Return files from the request as a MultiDictProxy.""" files = ((k, v) for k, v in req.POST.items() if hasattr(v, "file")) return self._makeproxy(MultiDict(files), schema) def load_matchdict(self, req, schema): """Return the request's ``matchdict`` as a MultiDictProxy.""" return self._makeproxy(req.matchdict, schema) def handle_error(self, error, req, schema, *, error_status_code, error_headers): """Handles errors during parsing. Aborts the current HTTP request and responds with a 400 error. """ status_code = error_status_code or self.DEFAULT_VALIDATION_STATUS response = exception_response( status_code, detail=str(error), headers=error_headers, content_type="application/json", ) body = json.dumps(error.messages) response.body = body.encode("utf-8") if isinstance(body, str) else body raise response def _handle_invalid_json_error(self, error, req, *args, **kwargs): messages = {"json": ["Invalid JSON body."]} response = exception_response( 400, detail=str(messages), content_type="application/json" ) body = json.dumps(messages) response.body = body.encode("utf-8") if isinstance(body, str) else body raise response def use_args( self, argmap, req=None, *, location=core.Parser.DEFAULT_LOCATION, unknown=None, as_kwargs=False, validate=None, error_status_code=None, error_headers=None ): """Decorator that injects parsed arguments into a view callable. Supports the *Class-based View* pattern where `request` is saved as an instance attribute on a view class. :param dict argmap: Either a `marshmallow.Schema`, a `dict` of argname -> `marshmallow.fields.Field` pairs, or a callable which accepts a request and returns a `marshmallow.Schema`. :param req: The request object to parse. Pulled off of the view by default. :param str location: Where on the request to load values. :param str unknown: A value to pass for ``unknown`` when calling the schema's ``load`` method. :param bool as_kwargs: Whether to insert arguments as keyword arguments. :param callable validate: Validation function that receives the dictionary of parsed arguments. If the function returns ``False``, the parser will raise a :exc:`ValidationError`. :param int error_status_code: Status code passed to error handler functions when a `ValidationError` is raised. :param dict error_headers: Headers passed to error handler functions when a a `ValidationError` is raised. """ location = location or self.location # Optimization: If argmap is passed as a dictionary, we only need # to generate a Schema once if isinstance(argmap, Mapping): argmap = self.schema_class.from_dict(argmap)() def decorator(func): @functools.wraps(func) def wrapper(obj, *args, **kwargs): # The first argument is either `self` or `request` try: # get self.request request = req or obj.request except AttributeError: # first arg is request request = obj # NOTE: At this point, argmap may be a Schema, callable, or dict parsed_args = self.parse( argmap, req=request, location=location, unknown=unknown, validate=validate, error_status_code=error_status_code, error_headers=error_headers, ) args, kwargs = self._update_args_kwargs( args, kwargs, parsed_args, as_kwargs ) return func(obj, *args, **kwargs) wrapper.__wrapped__ = func return wrapper return decorator parser = PyramidParser() use_args = parser.use_args use_kwargs = parser.use_kwargs python-webargs_8.0.1.orig/src/webargs/testing.py0000644000000000000000000002320513775063555016732 0ustar00"""Utilities for testing. Includes a base test class for testing parsers. .. warning:: Methods and functions in this module may change without warning and without a major version change. """ import pytest import webtest from webargs.core import json class CommonTestCase: """Base test class that defines test methods for common functionality across all parsers. Subclasses must define `create_app`, which returns a WSGI-like app. """ def create_app(self): """Return a WSGI app""" raise NotImplementedError("Must define create_app()") def create_testapp(self, app): return webtest.TestApp(app) def before_create_app(self): pass def after_create_app(self): pass @pytest.fixture(scope="class") def testapp(self): self.before_create_app() yield self.create_testapp(self.create_app()) self.after_create_app() def test_parse_querystring_args(self, testapp): assert testapp.get("/echo?name=Fred").json == {"name": "Fred"} def test_parse_form(self, testapp): assert testapp.post("/echo_form", {"name": "Joe"}).json == {"name": "Joe"} def test_parse_json(self, testapp): assert testapp.post_json("/echo_json", {"name": "Fred"}).json == { "name": "Fred" } def test_parse_json_missing(self, testapp): assert testapp.post("/echo_json", "").json == {"name": "World"} def test_parse_json_or_form(self, testapp): assert testapp.post_json("/echo_json_or_form", {"name": "Fred"}).json == { "name": "Fred" } assert testapp.post("/echo_json_or_form", {"name": "Joe"}).json == { "name": "Joe" } assert testapp.post("/echo_json_or_form", "").json == {"name": "World"} def test_parse_querystring_default(self, testapp): assert testapp.get("/echo").json == {"name": "World"} def test_parse_json_with_charset(self, testapp): res = testapp.post( "/echo_json", json.dumps({"name": "Steve"}), content_type="application/json;charset=UTF-8", ) assert res.json == {"name": "Steve"} def test_parse_json_with_vendor_media_type(self, testapp): res = testapp.post( "/echo_json", json.dumps({"name": "Steve"}), content_type="application/vnd.api+json;charset=UTF-8", ) assert res.json == {"name": "Steve"} def test_parse_ignore_extra_data(self, testapp): assert testapp.post_json( "/echo_ignoring_extra_data", {"extra": "data"} ).json == {"name": "World"} def test_parse_json_empty(self, testapp): assert testapp.post_json("/echo_json", {}).json == {"name": "World"} def test_parse_json_error_unexpected_int(self, testapp): res = testapp.post_json("/echo_json", 1, expect_errors=True) assert res.status_code == 422 def test_parse_json_error_unexpected_list(self, testapp): res = testapp.post_json("/echo_json", [{"extra": "data"}], expect_errors=True) assert res.status_code == 422 def test_parse_json_many_schema_invalid_input(self, testapp): res = testapp.post_json( "/echo_many_schema", [{"name": "a"}], expect_errors=True ) assert res.status_code == 422 def test_parse_json_many_schema(self, testapp): res = testapp.post_json("/echo_many_schema", [{"name": "Steve"}]).json assert res == [{"name": "Steve"}] def test_parse_json_many_schema_error_malformed_data(self, testapp): res = testapp.post_json( "/echo_many_schema", {"extra": "data"}, expect_errors=True ) assert res.status_code == 422 def test_parsing_form_default(self, testapp): assert testapp.post("/echo_form", {}).json == {"name": "World"} def test_parse_querystring_multiple(self, testapp): expected = {"name": ["steve", "Loria"]} assert testapp.get("/echo_multi?name=steve&name=Loria").json == expected # test that passing a single value parses correctly # on parsers like falconparser, where there is no native MultiDict type, # this verifies the usage of MultiDictProxy to ensure that single values # are "listified" def test_parse_querystring_multiple_single_value(self, testapp): expected = {"name": ["steve"]} assert testapp.get("/echo_multi?name=steve").json == expected def test_parse_form_multiple(self, testapp): expected = {"name": ["steve", "Loria"]} assert ( testapp.post("/echo_multi_form", {"name": ["steve", "Loria"]}).json == expected ) def test_parse_json_list(self, testapp): expected = {"name": ["Steve"]} assert ( testapp.post_json("/echo_multi_json", {"name": ["Steve"]}).json == expected ) def test_parse_json_list_error_malformed_data(self, testapp): res = testapp.post_json( "/echo_multi_json", {"name": "Steve"}, expect_errors=True ) assert res.status_code == 422 def test_parse_json_with_nonascii_chars(self, testapp): text = "øˆƒ£ºº∆ƒˆ∆" assert testapp.post_json("/echo_json", {"name": text}).json == {"name": text} # https://github.com/marshmallow-code/webargs/issues/427 def test_parse_json_with_nonutf8_chars(self, testapp): res = testapp.post( "/echo_json", b"\xfe", headers={"Accept": "application/json", "Content-Type": "application/json"}, expect_errors=True, ) assert res.status_code == 400 assert res.json == {"json": ["Invalid JSON body."]} def test_validation_error_returns_422_response(self, testapp): res = testapp.post_json("/echo_json", {"name": "b"}, expect_errors=True) assert res.status_code == 422 def test_user_validation_error_returns_422_response_by_default(self, testapp): res = testapp.post_json("/error", {"text": "foo"}, expect_errors=True) assert res.status_code == 422 def test_use_args_decorator(self, testapp): assert testapp.get("/echo_use_args?name=Fred").json == {"name": "Fred"} def test_use_args_with_path_param(self, testapp): url = "/echo_use_args_with_path_param/foo" res = testapp.get(url + "?value=42") assert res.json == {"value": 42} def test_use_args_with_validation(self, testapp): result = testapp.post("/echo_use_args_validated", {"value": 43}) assert result.status_code == 200 result = testapp.post( "/echo_use_args_validated", {"value": 41}, expect_errors=True ) assert result.status_code == 422 def test_use_kwargs_decorator(self, testapp): assert testapp.get("/echo_use_kwargs?name=Fred").json == {"name": "Fred"} def test_use_kwargs_with_path_param(self, testapp): url = "/echo_use_kwargs_with_path_param/foo" res = testapp.get(url + "?value=42") assert res.json == {"value": 42} def test_parsing_headers(self, testapp): res = testapp.get("/echo_headers", headers={"name": "Fred"}) assert res.json == {"name": "Fred"} def test_parsing_cookies(self, testapp): testapp.set_cookie("name", "Steve") res = testapp.get("/echo_cookie") assert res.json == {"name": "Steve"} def test_parse_nested_json(self, testapp): res = testapp.post_json( "/echo_nested", {"name": {"first": "Steve", "last": "Loria"}} ) assert res.json == {"name": {"first": "Steve", "last": "Loria"}} def test_parse_nested_many_json(self, testapp): in_data = {"users": [{"id": 1, "name": "foo"}, {"id": 2, "name": "bar"}]} res = testapp.post_json("/echo_nested_many", in_data) assert res.json == in_data # Regression test for https://github.com/marshmallow-code/webargs/issues/120 def test_parse_nested_many_missing(self, testapp): in_data = {} res = testapp.post_json("/echo_nested_many", in_data) assert res.json == {} def test_parse_files(self, testapp): res = testapp.post( "/echo_file", {"myfile": webtest.Upload("README.rst", b"data")} ) assert res.json == {"myfile": "data"} # https://github.com/sloria/webargs/pull/297 def test_empty_json(self, testapp): res = testapp.post("/echo_json") assert res.status_code == 200 assert res.json == {"name": "World"} # https://github.com/sloria/webargs/pull/297 def test_empty_json_with_headers(self, testapp): res = testapp.post( "/echo_json", "", headers={"Accept": "application/json", "Content-Type": "application/json"}, ) assert res.status_code == 200 assert res.json == {"name": "World"} # https://github.com/sloria/webargs/issues/329 def test_invalid_json(self, testapp): res = testapp.post( "/echo_json", '{"foo": "bar", }', headers={"Accept": "application/json", "Content-Type": "application/json"}, expect_errors=True, ) assert res.status_code == 400 assert res.json == {"json": ["Invalid JSON body."]} @pytest.mark.parametrize( ("path", "payload", "content_type"), [ ( "/echo_json", json.dumps({"name": "foo"}), "application/x-www-form-urlencoded", ), ("/echo_form", {"name": "foo"}, "application/json"), ], ) def test_content_type_mismatch(self, testapp, path, payload, content_type): res = testapp.post(path, payload, headers={"Content-Type": content_type}) assert res.json == {"name": "World"} python-webargs_8.0.1.orig/src/webargs/tornadoparser.py0000644000000000000000000001222014163275625020126 0ustar00"""Tornado request argument parsing module. Example: :: import tornado.web from marshmallow import fields from webargs.tornadoparser import use_args class HelloHandler(tornado.web.RequestHandler): @use_args({'name': fields.Str(missing='World')}) def get(self, args): response = {'message': 'Hello {}'.format(args['name'])} self.write(response) """ import tornado.web import tornado.concurrent from tornado.escape import _unicode from webargs import core from webargs.multidictproxy import MultiDictProxy class HTTPError(tornado.web.HTTPError): """`tornado.web.HTTPError` that stores validation errors.""" def __init__(self, *args, **kwargs): self.messages = kwargs.pop("messages", {}) self.headers = kwargs.pop("headers", None) super().__init__(*args, **kwargs) def is_json_request(req): content_type = req.headers.get("Content-Type") return content_type is not None and core.is_json(content_type) class WebArgsTornadoMultiDictProxy(MultiDictProxy): """ Override class for Tornado multidicts, handles argument decoding requirements. """ def __getitem__(self, key): try: value = self.data.get(key, core.missing) if value is core.missing: return core.missing if key in self.multiple_keys: return [ _unicode(v) if isinstance(v, (str, bytes)) else v for v in value ] if value and isinstance(value, (list, tuple)): value = value[0] if isinstance(value, (str, bytes)): return _unicode(value) return value # based on tornado.web.RequestHandler.decode_argument except UnicodeDecodeError: raise HTTPError(400, "Invalid unicode in {}: {!r}".format(key, value[:40])) class WebArgsTornadoCookiesMultiDictProxy(MultiDictProxy): """ And a special override for cookies because they come back as objects with a `value` attribute we need to extract. Also, does not use the `_unicode` decoding step """ def __getitem__(self, key): cookie = self.data.get(key, core.missing) if cookie is core.missing: return core.missing if key in self.multiple_keys: return [cookie.value] return cookie.value class TornadoParser(core.Parser): """Tornado request argument parser.""" def _raw_load_json(self, req): """Return a json payload from the request for the core parser's load_json Checks the input mimetype and may return 'missing' if the mimetype is non-json, even if the request body is parseable as json.""" if not is_json_request(req): return core.missing # request.body may be a concurrent.Future on streaming requests # this would cause a TypeError if we try to parse it if isinstance(req.body, tornado.concurrent.Future): return core.missing return core.parse_json(req.body) def load_querystring(self, req, schema): """Return query params from the request as a MultiDictProxy.""" return self._makeproxy( req.query_arguments, schema, cls=WebArgsTornadoMultiDictProxy ) def load_form(self, req, schema): """Return form values from the request as a MultiDictProxy.""" return self._makeproxy( req.body_arguments, schema, cls=WebArgsTornadoMultiDictProxy ) def load_headers(self, req, schema): """Return headers from the request as a MultiDictProxy.""" return self._makeproxy(req.headers, schema, cls=WebArgsTornadoMultiDictProxy) def load_cookies(self, req, schema): """Return cookies from the request as a MultiDictProxy.""" # use the specialized subclass specifically for handling Tornado # cookies return self._makeproxy( req.cookies, schema, cls=WebArgsTornadoCookiesMultiDictProxy ) def load_files(self, req, schema): """Return files from the request as a MultiDictProxy.""" return self._makeproxy(req.files, schema, cls=WebArgsTornadoMultiDictProxy) def handle_error(self, error, req, schema, *, error_status_code, error_headers): """Handles errors during parsing. Raises a `tornado.web.HTTPError` with a 400 error. """ status_code = error_status_code or self.DEFAULT_VALIDATION_STATUS if status_code == 422: reason = "Unprocessable Entity" else: reason = None raise HTTPError( status_code, log_message=str(error.messages), reason=reason, messages=error.messages, headers=error_headers, ) def _handle_invalid_json_error(self, error, req, *args, **kwargs): raise HTTPError( 400, log_message="Invalid JSON body.", reason="Bad Request", messages={"json": ["Invalid JSON body."]}, ) def get_request_from_view_args(self, view, args, kwargs): return args[0].request parser = TornadoParser() use_args = parser.use_args use_kwargs = parser.use_kwargs python-webargs_8.0.1.orig/tests/__init__.py0000755000000000000000000000000013775063555015724 0ustar00python-webargs_8.0.1.orig/tests/apps/0000755000000000000000000000000013326636200014546 5ustar00python-webargs_8.0.1.orig/tests/conftest.py0000644000000000000000000000010113433772441016001 0ustar00import pytest pytest.register_assert_rewrite("webargs.testing") python-webargs_8.0.1.orig/tests/test_aiohttpparser.py0000644000000000000000000000732413775063555020126 0ustar00from io import BytesIO from unittest import mock import webtest import webtest_aiohttp import pytest from webargs import fields from webargs.aiohttpparser import AIOHTTPParser from webargs.testing import CommonTestCase from tests.apps.aiohttp_app import create_app @pytest.fixture def web_request(): req = mock.Mock() req.query = {} yield req req.query = {} class TestAIOHTTPParser(CommonTestCase): def create_app(self): return create_app() def create_testapp(self, app, loop): return webtest_aiohttp.TestApp(app, loop=loop) @pytest.fixture def testapp(self, loop): return self.create_testapp(self.create_app(), loop) @pytest.mark.skip(reason="files location not supported for aiohttpparser") def test_parse_files(self, testapp): pass def test_parse_match_info(self, testapp): assert testapp.get("/echo_match_info/42").json == {"mymatch": 42} def test_use_args_on_method_handler(self, testapp): assert testapp.get("/echo_method").json == {"name": "World"} assert testapp.get("/echo_method?name=Steve").json == {"name": "Steve"} assert testapp.get("/echo_method_view").json == {"name": "World"} assert testapp.get("/echo_method_view?name=Steve").json == {"name": "Steve"} # regression test for https://github.com/marshmallow-code/webargs/issues/165 def test_multiple_args(self, testapp): res = testapp.post_json("/echo_multiple_args", {"first": "1", "last": "2"}) assert res.json == {"first": "1", "last": "2"} # regression test for https://github.com/marshmallow-code/webargs/issues/145 def test_nested_many_with_data_key(self, testapp): res = testapp.post_json("/echo_nested_many_data_key", {"X-Field": [{"id": 24}]}) assert res.json == {"x_field": [{"id": 24}]} res = testapp.post_json("/echo_nested_many_data_key", {}) assert res.json == {} def test_schema_as_kwargs_view(self, testapp): assert testapp.get("/echo_use_schema_as_kwargs").json == {"name": "World"} assert testapp.get("/echo_use_schema_as_kwargs?name=Chandler").json == { "name": "Chandler" } # https://github.com/marshmallow-code/webargs/pull/297 def test_empty_json_body(self, testapp): environ = {"CONTENT_TYPE": "application/json", "wsgi.input": BytesIO(b"")} req = webtest.TestRequest.blank("/echo", environ) resp = testapp.do_request(req) assert resp.json == {"name": "World"} def test_use_args_multiple(self, testapp): res = testapp.post_json( "/echo_use_args_multiple?page=2&q=10", {"name": "Steve"} ) assert res.json == { "query_parsed": {"page": 2, "q": 10}, "json_parsed": {"name": "Steve"}, } async def test_aiohttpparser_synchronous_error_handler(web_request): parser = AIOHTTPParser() class CustomError(Exception): pass @parser.error_handler def custom_handle_error(error, req, schema, *, error_status_code, error_headers): raise CustomError("foo") with pytest.raises(CustomError): await parser.parse( {"foo": fields.Int(required=True)}, web_request, location="query" ) async def test_aiohttpparser_asynchronous_error_handler(web_request): parser = AIOHTTPParser() class CustomError(Exception): pass @parser.error_handler async def custom_handle_error( error, req, schema, *, error_status_code, error_headers ): async def inner(): raise CustomError("foo") await inner() with pytest.raises(CustomError): await parser.parse( {"foo": fields.Int(required=True)}, web_request, location="query" ) python-webargs_8.0.1.orig/tests/test_bottleparser.py0000644000000000000000000000052513421337646017734 0ustar00import pytest from .apps.bottle_app import app from webargs.testing import CommonTestCase class TestBottleParser(CommonTestCase): def create_app(self): return app @pytest.mark.skip(reason="Parsing vendor media types is not supported in bottle") def test_parse_json_with_vendor_media_type(self, testapp): pass python-webargs_8.0.1.orig/tests/test_core.py0000644000000000000000000013037314163275625016165 0ustar00import datetime import typing from unittest import mock import pytest from marshmallow import ( Schema, post_load, pre_load, validates_schema, EXCLUDE, INCLUDE, RAISE, ) from werkzeug.datastructures import MultiDict as WerkMultiDict from django.utils.datastructures import MultiValueDict as DjMultiDict from bottle import MultiDict as BotMultiDict from webargs import fields, ValidationError from webargs.core import ( Parser, is_json, get_mimetype, ) from webargs.multidictproxy import MultiDictProxy class MockHTTPError(Exception): def __init__(self, status_code, headers): self.status_code = status_code self.headers = headers super().__init__(self, "HTTP Error occurred") class MockRequestParser(Parser): """A minimal parser implementation that parses mock requests.""" def load_querystring(self, req, schema): return self._makeproxy(req.query, schema) def load_form(self, req, schema): return MultiDictProxy(req.form, schema) def load_json(self, req, schema): return req.json def load_cookies(self, req, schema): return req.cookies @pytest.yield_fixture(scope="function") def web_request(): req = mock.Mock() req.query = {} yield req req.query = {} @pytest.fixture def parser(): return MockRequestParser() # Parser tests @mock.patch("webargs.core.Parser.load_json") def test_load_json_called_by_parse_default(load_json, web_request): schema = Schema.from_dict({"foo": fields.Field()})() load_json.return_value = {"foo": 1} p = Parser() p.parse(schema, web_request) load_json.assert_called_with(web_request, schema) @pytest.mark.parametrize( "location", ["querystring", "form", "headers", "cookies", "files"] ) def test_load_nondefault_called_by_parse_with_location(location, web_request): with mock.patch( f"webargs.core.Parser.load_{location}" ) as mock_loadfunc, mock.patch("webargs.core.Parser.load_json") as load_json: mock_loadfunc.return_value = {} load_json.return_value = {} p = Parser() # ensure that without location=..., the loader is not called (json is # called) p.parse({"foo": fields.Field()}, web_request) assert mock_loadfunc.call_count == 0 assert load_json.call_count == 1 # but when location=... is given, the loader *is* called and json is # not called p.parse({"foo": fields.Field()}, web_request, location=location) assert mock_loadfunc.call_count == 1 # it was already 1, should not go up assert load_json.call_count == 1 def test_parse(parser, web_request): web_request.json = {"username": 42, "password": 42} argmap = {"username": fields.Field(), "password": fields.Field()} ret = parser.parse(argmap, web_request) assert {"username": 42, "password": 42} == ret @pytest.mark.parametrize( "set_location", [ "schema_instance", "parse_call", "parser_default", "parser_class_default", ], ) def test_parse_with_unknown_behavior_specified(parser, web_request, set_location): web_request.json = {"username": 42, "password": 42, "fjords": 42} class CustomSchema(Schema): username = fields.Field() password = fields.Field() def parse_with_desired_behavior(value): if set_location == "schema_instance": if value is not None: # pass 'unknown=None' to parse() in order to indicate that the # schema setting should be respected return parser.parse( CustomSchema(unknown=value), web_request, unknown=None ) else: return parser.parse(CustomSchema(), web_request) elif set_location == "parse_call": return parser.parse(CustomSchema(), web_request, unknown=value) elif set_location == "parser_default": parser.unknown = value return parser.parse(CustomSchema(), web_request) elif set_location == "parser_class_default": class CustomParser(MockRequestParser): DEFAULT_UNKNOWN_BY_LOCATION = {"json": value} return CustomParser().parse(CustomSchema(), web_request) else: raise NotImplementedError # with no unknown setting or unknown=RAISE, it blows up with pytest.raises(ValidationError, match="Unknown field."): parse_with_desired_behavior(None) with pytest.raises(ValidationError, match="Unknown field."): parse_with_desired_behavior(RAISE) # with unknown=EXCLUDE the data is omitted ret = parse_with_desired_behavior(EXCLUDE) assert {"username": 42, "password": 42} == ret # with unknown=INCLUDE it is added even though it isn't part of the schema ret = parse_with_desired_behavior(INCLUDE) assert {"username": 42, "password": 42, "fjords": 42} == ret def test_parse_with_explicit_unknown_overrides_schema(parser, web_request): web_request.json = {"username": 42, "password": 42, "fjords": 42} class CustomSchema(Schema): username = fields.Field() password = fields.Field() # setting RAISE in the parse call overrides schema setting with pytest.raises(ValidationError, match="Unknown field."): parser.parse(CustomSchema(unknown=EXCLUDE), web_request, unknown=RAISE) with pytest.raises(ValidationError, match="Unknown field."): parser.parse(CustomSchema(unknown=INCLUDE), web_request, unknown=RAISE) # and the reverse -- setting EXCLUDE or INCLUDE in the parse call overrides # a schema with RAISE already set ret = parser.parse(CustomSchema(unknown=RAISE), web_request, unknown=EXCLUDE) assert {"username": 42, "password": 42} == ret ret = parser.parse(CustomSchema(unknown=RAISE), web_request, unknown=INCLUDE) assert {"username": 42, "password": 42, "fjords": 42} == ret @pytest.mark.parametrize("clear_method", ["custom_class", "instance_setting", "both"]) def test_parse_with_default_unknown_cleared_uses_schema_value( parser, web_request, clear_method ): web_request.json = {"username": 42, "password": 42, "fjords": 42} class CustomSchema(Schema): username = fields.Field() password = fields.Field() if clear_method == "custom_class": class CustomParser(MockRequestParser): DEFAULT_UNKNOWN_BY_LOCATION = {} parser = CustomParser() elif clear_method == "instance_setting": parser = MockRequestParser(unknown=None) elif clear_method == "both": # setting things in multiple ways should not result in errors class CustomParser(MockRequestParser): DEFAULT_UNKNOWN_BY_LOCATION = {} parser = CustomParser(unknown=None) else: raise NotImplementedError with pytest.raises(ValidationError, match="Unknown field."): parser.parse(CustomSchema(), web_request) with pytest.raises(ValidationError, match="Unknown field."): parser.parse(CustomSchema(unknown=RAISE), web_request) ret = parser.parse(CustomSchema(unknown=EXCLUDE), web_request) assert {"username": 42, "password": 42} == ret ret = parser.parse(CustomSchema(unknown=INCLUDE), web_request) assert {"username": 42, "password": 42, "fjords": 42} == ret def test_parse_required_arg_raises_validation_error(parser, web_request): web_request.json = {} args = {"foo": fields.Field(required=True)} with pytest.raises(ValidationError, match="Missing data for required field."): parser.parse(args, web_request) def test_arg_not_required_excluded_in_parsed_output(parser, web_request): web_request.json = {"first": "Steve"} args = {"first": fields.Str(), "last": fields.Str()} result = parser.parse(args, web_request) assert result == {"first": "Steve"} def test_arg_allow_none(parser, web_request): web_request.json = {"first": "Steve", "last": None} args = {"first": fields.Str(), "last": fields.Str(allow_none=True)} result = parser.parse(args, web_request) assert result == {"first": "Steve", "last": None} def test_parse_required_arg(parser, web_request): web_request.json = {"foo": 42} result = parser.parse({"foo": fields.Field(required=True)}, web_request) assert result == {"foo": 42} def test_parse_required_list(parser, web_request): web_request.json = {"bar": []} args = {"foo": fields.List(fields.Field(), required=True)} with pytest.raises(ValidationError) as excinfo: parser.parse(args, web_request) assert ( excinfo.value.messages["json"]["foo"][0] == "Missing data for required field." ) # Regression test for https://github.com/marshmallow-code/webargs/issues/107 def test_parse_list_allow_none(parser, web_request): web_request.json = {"foo": None} args = {"foo": fields.List(fields.Field(allow_none=True), allow_none=True)} assert parser.parse(args, web_request) == {"foo": None} def test_parse_list_dont_allow_none(parser, web_request): web_request.json = {"foo": None} args = {"foo": fields.List(fields.Field(), allow_none=False)} with pytest.raises(ValidationError) as excinfo: parser.parse(args, web_request) assert excinfo.value.messages["json"]["foo"][0] == "Field may not be null." def test_parse_empty_list(parser, web_request): web_request.json = {"things": []} args = {"things": fields.List(fields.Field())} assert parser.parse(args, web_request) == {"things": []} def test_parse_missing_list(parser, web_request): web_request.json = {} args = {"things": fields.List(fields.Field())} assert parser.parse(args, web_request) == {} def test_default_location(): assert Parser.DEFAULT_LOCATION == "json" def test_missing_with_default(parser, web_request): web_request.json = {} args = {"val": fields.Field(missing="pizza")} result = parser.parse(args, web_request) assert result["val"] == "pizza" def test_default_can_be_none(parser, web_request): web_request.json = {} args = {"val": fields.Field(missing=None, allow_none=True)} result = parser.parse(args, web_request) assert result["val"] is None # Regression test for issue #11 def test_arg_with_default_and_location(parser, web_request): web_request.json = {} args = { "p": fields.Int( missing=1, validate=lambda p: p > 0, error="La page demandée n'existe pas", location="querystring", ) } assert parser.parse(args, web_request) == {"p": 1} def test_value_error_raised_if_parse_called_with_invalid_location(parser, web_request): field = fields.Field() with pytest.raises(ValueError, match="Invalid location argument: invalidlocation"): parser.parse({"foo": field}, web_request, location="invalidlocation") @mock.patch("webargs.core.Parser.handle_error") def test_handle_error_called_when_parsing_raises_error(handle_error, web_request): # handle_error must raise an error to be valid handle_error.side_effect = ValidationError("parsing failed") def always_fail(*args, **kwargs): raise ValidationError("error occurred") p = Parser() assert handle_error.call_count == 0 with pytest.raises(ValidationError): p.parse({"foo": fields.Field()}, web_request, validate=always_fail) assert handle_error.call_count == 1 with pytest.raises(ValidationError): p.parse({"foo": fields.Field()}, web_request, validate=always_fail) assert handle_error.call_count == 2 def test_handle_error_reraises_errors(web_request): p = Parser() with pytest.raises(ValidationError): p.handle_error( ValidationError("error raised"), web_request, Schema(), error_status_code=422, error_headers={}, ) @mock.patch("webargs.core.Parser.load_headers") def test_location_as_init_argument(load_headers, web_request): p = Parser(location="headers") load_headers.return_value = {} p.parse({"foo": fields.Field()}, web_request) assert load_headers.called def test_custom_error_handler(web_request): class CustomError(Exception): pass def error_handler(error, req, schema, *, error_status_code, error_headers): assert isinstance(schema, Schema) raise CustomError(error) def failing_validate_func(args): raise ValidationError("parsing failed") class MySchema(Schema): foo = fields.Int() myschema = MySchema() web_request.json = {"foo": "hello world"} p = Parser(error_handler=error_handler) with pytest.raises(CustomError): p.parse(myschema, web_request, validate=failing_validate_func) def test_custom_error_handler_decorator(web_request): class CustomError(Exception): pass mock_schema = mock.Mock(spec=Schema) mock_schema.strict = True mock_schema.load.side_effect = ValidationError("parsing json failed") parser = Parser() @parser.error_handler def handle_error(error, req, schema, *, error_status_code, error_headers): assert isinstance(schema, Schema) raise CustomError(error) with pytest.raises(CustomError): parser.parse(mock_schema, web_request) def test_custom_error_handler_must_reraise(web_request): class CustomError(Exception): pass mock_schema = mock.Mock(spec=Schema) mock_schema.strict = True mock_schema.load.side_effect = ValidationError("parsing json failed") parser = Parser() @parser.error_handler def handle_error(error, req, schema, *, error_status_code, error_headers): pass # because the handler above does not raise a new error, the parser should # raise a ValueError -- indicating a programming error with pytest.raises(ValueError): parser.parse(mock_schema, web_request) def test_custom_location_loader(web_request): web_request.data = {"foo": 42} parser = Parser() @parser.location_loader("data") def load_data(req, schema): return req.data result = parser.parse({"foo": fields.Int()}, web_request, location="data") assert result["foo"] == 42 def test_custom_location_loader_with_data_key(web_request): web_request.data = {"X-Foo": 42} parser = Parser() @parser.location_loader("data") def load_data(req, schema): return req.data result = parser.parse( {"x_foo": fields.Int(data_key="X-Foo")}, web_request, location="data" ) assert result["x_foo"] == 42 def test_full_input_validation(parser, web_request): web_request.json = {"foo": 41, "bar": 42} args = {"foo": fields.Int(), "bar": fields.Int()} with pytest.raises(ValidationError): # Test that `validate` receives dictionary of args parser.parse(args, web_request, validate=lambda args: args["foo"] > args["bar"]) def test_full_input_validation_with_multiple_validators(web_request, parser): def validate1(args): if args["a"] > args["b"]: raise ValidationError("b must be > a") def validate2(args): if args["b"] > args["a"]: raise ValidationError("a must be > b") args = {"a": fields.Int(), "b": fields.Int()} web_request.json = {"a": 2, "b": 1} validators = [validate1, validate2] with pytest.raises(ValidationError, match="b must be > a"): parser.parse(args, web_request, validate=validators) web_request.json = {"a": 1, "b": 2} with pytest.raises(ValidationError, match="a must be > b"): parser.parse(args, web_request, validate=validators) def test_required_with_custom_error(parser, web_request): web_request.json = {} args = { "foo": fields.Str(required=True, error_messages={"required": "We need foo"}) } with pytest.raises(ValidationError) as excinfo: # Test that `validate` receives dictionary of args parser.parse(args, web_request) assert "We need foo" in excinfo.value.messages["json"]["foo"] def test_required_with_custom_error_and_validation_error(parser, web_request): web_request.json = {"foo": ""} args = { "foo": fields.Str( required="We need foo", validate=lambda s: len(s) > 1, error_messages={"validator_failed": "foo required length is 3"}, ) } with pytest.raises(ValidationError) as excinfo: # Test that `validate` receives dictionary of args parser.parse(args, web_request) assert "foo required length is 3" in excinfo.value.args[0]["foo"] def test_full_input_validator_receives_nonascii_input(web_request): def validate(val): return False text = "øœ∑∆∑" web_request.json = {"text": text} parser = MockRequestParser() args = {"text": fields.Str()} with pytest.raises(ValidationError) as excinfo: parser.parse(args, web_request, validate=validate) assert excinfo.value.messages == {"json": ["Invalid value."]} def test_invalid_argument_for_validate(web_request, parser): with pytest.raises(ValueError) as excinfo: parser.parse({}, web_request, validate="notcallable") assert "not a callable or list of callables." in excinfo.value.args[0] def create_bottle_multi_dict(): d = BotMultiDict() d["foos"] = "a" d["foos"] = "b" return d multidicts = [ WerkMultiDict([("foos", "a"), ("foos", "b")]), create_bottle_multi_dict(), DjMultiDict({"foos": ["a", "b"]}), ] @pytest.mark.parametrize("input_dict", multidicts) def test_multidict_proxy(input_dict): class ListSchema(Schema): foos = fields.List(fields.Str()) class StrSchema(Schema): foos = fields.Str() # this MultiDictProxy is aware that "foos" is a list field and will # therefore produce a list with __getitem__ list_wrapped_multidict = MultiDictProxy(input_dict, ListSchema()) # this MultiDictProxy is under the impression that "foos" is just a string # and it should return "a" or "b" # the decision between "a" and "b" in this case belongs to the framework str_wrapped_multidict = MultiDictProxy(input_dict, StrSchema()) assert list_wrapped_multidict["foos"] == ["a", "b"] assert str_wrapped_multidict["foos"] in ("a", "b") def test_parse_with_data_key(web_request): web_request.json = {"Content-Type": "application/json"} parser = MockRequestParser() args = {"content_type": fields.Field(data_key="Content-Type")} parsed = parser.parse(args, web_request) assert parsed == {"content_type": "application/json"} def test_parse_with_data_key_retains_field_name_in_error(web_request): web_request.json = {"Content-Type": 12345} parser = MockRequestParser() args = {"content_type": fields.Str(data_key="Content-Type")} with pytest.raises(ValidationError) as excinfo: parser.parse(args, web_request) assert "json" in excinfo.value.messages assert "Content-Type" in excinfo.value.messages["json"] assert excinfo.value.messages["json"]["Content-Type"] == ["Not a valid string."] def test_parse_nested_with_data_key(web_request): parser = MockRequestParser() web_request.json = {"nested_arg": {"wrong": "OK"}} args = {"nested_arg": fields.Nested({"right": fields.Field(data_key="wrong")})} parsed = parser.parse(args, web_request) assert parsed == {"nested_arg": {"right": "OK"}} def test_parse_nested_with_missing_key_and_data_key(web_request): parser = MockRequestParser() web_request.json = {"nested_arg": {}} args = { "nested_arg": fields.Nested( {"found": fields.Field(missing=None, allow_none=True, data_key="miss")} ) } parsed = parser.parse(args, web_request) assert parsed == {"nested_arg": {"found": None}} def test_parse_nested_with_default(web_request): parser = MockRequestParser() web_request.json = {"nested_arg": {}} args = {"nested_arg": fields.Nested({"miss": fields.Field(missing="")})} parsed = parser.parse(args, web_request) assert parsed == {"nested_arg": {"miss": ""}} def test_nested_many(web_request, parser): web_request.json = {"pets": [{"name": "Pips"}, {"name": "Zula"}]} args = {"pets": fields.Nested({"name": fields.Str()}, required=True, many=True)} parsed = parser.parse(args, web_request) assert parsed == {"pets": [{"name": "Pips"}, {"name": "Zula"}]} web_request.json = {} with pytest.raises(ValidationError): parser.parse(args, web_request) def test_use_args(web_request, parser): user_args = {"username": fields.Str(), "password": fields.Str()} web_request.json = {"username": "foo", "password": "bar"} @parser.use_args(user_args, web_request) def viewfunc(args): return args assert viewfunc() == {"username": "foo", "password": "bar"} def test_use_args_stacked(web_request, parser): query_args = {"page": fields.Int()} json_args = {"username": fields.Str()} web_request.json = {"username": "foo"} web_request.query = {"page": 42} @parser.use_args(query_args, web_request, location="query") @parser.use_args(json_args, web_request) def viewfunc(query_parsed, json_parsed): return {"json": json_parsed, "query": query_parsed} assert viewfunc() == {"json": {"username": "foo"}, "query": {"page": 42}} def test_use_kwargs_stacked(web_request, parser): query_args = { "page": fields.Int(error_messages={"invalid": "{input} not a valid integer"}) } json_args = {"username": fields.Str()} web_request.json = {"username": "foo"} web_request.query = {"page": 42} @parser.use_kwargs(query_args, web_request, location="query") @parser.use_kwargs(json_args, web_request) def viewfunc(page, username): return {"json": {"username": username}, "query": {"page": page}} assert viewfunc() == {"json": {"username": "foo"}, "query": {"page": 42}} @pytest.mark.parametrize("decorator_name", ["use_args", "use_kwargs"]) def test_decorators_dont_change_docstring(parser, decorator_name): decorator = getattr(parser, decorator_name) @decorator({"val": fields.Int()}) def viewfunc(*args, **kwargs): """View docstring""" pass assert viewfunc.__doc__ == "View docstring" def test_list_allowed_missing(web_request, parser): args = {"name": fields.List(fields.Str())} web_request.json = {} result = parser.parse(args, web_request) assert result == {} def test_int_list_allowed_missing(web_request, parser): args = {"name": fields.List(fields.Int())} web_request.json = {} result = parser.parse(args, web_request) assert result == {} def test_multiple_arg_required_with_int_conversion(web_request, parser): args = {"ids": fields.List(fields.Int(), required=True)} web_request.json = {} with pytest.raises(ValidationError) as excinfo: parser.parse(args, web_request) assert excinfo.value.messages == { "json": {"ids": ["Missing data for required field."]} } def test_parse_with_callable(web_request, parser): web_request.json = {"foo": 42} class MySchema(Schema): foo = fields.Field() def make_schema(req): assert req is web_request return MySchema(context={"request": req}) result = parser.parse(make_schema, web_request) assert result == {"foo": 42} def test_use_args_callable(web_request, parser): class HelloSchema(Schema): name = fields.Str() @post_load def request_data(self, item, **kwargs): item["data"] = self.context["request"].data return item web_request.json = {"name": "foo"} web_request.data = "request-data" def make_schema(req): assert req is web_request return HelloSchema(context={"request": req}) @parser.use_args(make_schema, web_request) def viewfunc(args): return args assert viewfunc() == {"name": "foo", "data": "request-data"} class TestPassingSchema: class UserSchema(Schema): id = fields.Int(dump_only=True) email = fields.Email() password = fields.Str(load_only=True) def test_passing_schema_to_parse(self, parser, web_request): web_request.json = {"email": "foo@bar.com", "password": "bar"} result = parser.parse(self.UserSchema(), web_request) assert result == {"email": "foo@bar.com", "password": "bar"} def test_use_args_can_be_passed_a_schema(self, web_request, parser): web_request.json = {"email": "foo@bar.com", "password": "bar"} @parser.use_args(self.UserSchema(), web_request) def viewfunc(args): return args assert viewfunc() == {"email": "foo@bar.com", "password": "bar"} def test_passing_schema_factory_to_parse(self, parser, web_request): web_request.json = {"email": "foo@bar.com", "password": "bar"} def factory(req): assert req is web_request return self.UserSchema(context={"request": req}) result = parser.parse(factory, web_request) assert result == {"email": "foo@bar.com", "password": "bar"} def test_use_args_can_be_passed_a_schema_factory(self, web_request, parser): web_request.json = {"email": "foo@bar.com", "password": "bar"} def factory(req): assert req is web_request return self.UserSchema(context={"request": req}) @parser.use_args(factory, web_request) def viewfunc(args): return args assert viewfunc() == {"email": "foo@bar.com", "password": "bar"} def test_use_kwargs_can_be_passed_a_schema(self, web_request, parser): web_request.json = {"email": "foo@bar.com", "password": "bar"} @parser.use_kwargs(self.UserSchema(), web_request) def viewfunc(email, password): return {"email": email, "password": password} assert viewfunc() == {"email": "foo@bar.com", "password": "bar"} def test_use_kwargs_can_be_passed_a_schema_factory(self, web_request, parser): web_request.json = {"email": "foo@bar.com", "password": "bar"} def factory(req): assert req is web_request return self.UserSchema(context={"request": req}) @parser.use_kwargs(factory, web_request) def viewfunc(email, password): return {"email": email, "password": password} assert viewfunc() == {"email": "foo@bar.com", "password": "bar"} def test_use_kwargs_stacked(self, web_request, parser): web_request.json = {"email": "foo@bar.com", "password": "bar", "page": 42} @parser.use_kwargs({"page": fields.Int()}, web_request, unknown=EXCLUDE) @parser.use_kwargs(self.UserSchema(), web_request, unknown=EXCLUDE) def viewfunc(email, password, page): return {"email": email, "password": password, "page": page} assert viewfunc() == {"email": "foo@bar.com", "password": "bar", "page": 42} # Regression test for https://github.com/marshmallow-code/webargs/issues/146 def test_parse_does_not_add_missing_values_to_schema_validator( self, web_request, parser ): class UserSchema(Schema): name = fields.Str() location = fields.Field(required=False) @validates_schema(pass_original=True) def validate_schema(self, data, original_data, **kwargs): assert "location" not in original_data return True web_request.json = {"name": "Eric Cartman"} res = parser.parse(UserSchema, web_request) assert res == {"name": "Eric Cartman"} def test_use_args_with_custom_location_in_parser(web_request, parser): custom_args = {"foo": fields.Str()} web_request.json = {} parser.location = "custom" @parser.location_loader("custom") def load_custom(schema, req): return {"foo": "bar"} @parser.use_args(custom_args, web_request) def viewfunc(args): return args assert viewfunc() == {"foo": "bar"} def test_use_kwargs(web_request, parser): user_args = {"username": fields.Str(), "password": fields.Str()} web_request.json = {"username": "foo", "password": "bar"} @parser.use_kwargs(user_args, web_request) def viewfunc(username, password): return {"username": username, "password": password} assert viewfunc() == {"username": "foo", "password": "bar"} def test_use_kwargs_with_arg_missing(web_request, parser): user_args = {"username": fields.Str(required=True), "password": fields.Str()} web_request.json = {"username": "foo"} @parser.use_kwargs(user_args, web_request) def viewfunc(username, **kwargs): assert "password" not in kwargs return {"username": username} assert viewfunc() == {"username": "foo"} def test_delimited_list_empty_string(web_request, parser): web_request.json = {"dates": ""} schema_cls = Schema.from_dict({"dates": fields.DelimitedList(fields.Str())}) schema = schema_cls() parsed = parser.parse(schema, web_request) assert parsed["dates"] == [] data = schema.dump(parsed) assert data["dates"] == "" def test_delimited_list_default_delimiter(web_request, parser): web_request.json = {"ids": "1,2,3"} schema_cls = Schema.from_dict({"ids": fields.DelimitedList(fields.Int())}) schema = schema_cls() parsed = parser.parse(schema, web_request) assert parsed["ids"] == [1, 2, 3] data = schema.dump(parsed) assert data["ids"] == "1,2,3" def test_delimited_tuple_default_delimiter(web_request, parser): """ Test load and dump from DelimitedTuple, including the use of a datetime type (similar to a DelimitedList test below) which confirms that we aren't relying on __str__, but are properly de/serializing the included fields """ web_request.json = {"ids": "1,2,2020-05-04"} schema_cls = Schema.from_dict( { "ids": fields.DelimitedTuple( (fields.Int, fields.Int, fields.DateTime(format="%Y-%m-%d")) ) } ) schema = schema_cls() parsed = parser.parse(schema, web_request) assert parsed["ids"] == (1, 2, datetime.datetime(2020, 5, 4)) data = schema.dump(parsed) assert data["ids"] == "1,2,2020-05-04" def test_delimited_tuple_incorrect_arity(web_request, parser): web_request.json = {"ids": "1,2"} schema_cls = Schema.from_dict( {"ids": fields.DelimitedTuple((fields.Int, fields.Int, fields.Int))} ) schema = schema_cls() with pytest.raises(ValidationError): parser.parse(schema, web_request) def test_delimited_list_with_datetime(web_request, parser): """ Test that DelimitedList(DateTime(format=...)) correctly parses and dumps dates to and from strings -- indicates that we're doing proper serialization of values in dump() and not just relying on __str__ producing correct results """ web_request.json = {"dates": "2018-11-01,2018-11-02"} schema_cls = Schema.from_dict( {"dates": fields.DelimitedList(fields.DateTime(format="%Y-%m-%d"))} ) schema = schema_cls() parsed = parser.parse(schema, web_request) assert parsed["dates"] == [ datetime.datetime(2018, 11, 1), datetime.datetime(2018, 11, 2), ] data = schema.dump(parsed) assert data["dates"] == "2018-11-01,2018-11-02" def test_delimited_list_custom_delimiter(web_request, parser): web_request.json = {"ids": "1|2|3"} schema_cls = Schema.from_dict( {"ids": fields.DelimitedList(fields.Int(), delimiter="|")} ) schema = schema_cls() parsed = parser.parse(schema, web_request) assert parsed["ids"] == [1, 2, 3] data = schema.dump(parsed) assert data["ids"] == "1|2|3" def test_delimited_tuple_custom_delimiter(web_request, parser): web_request.json = {"ids": "1|2"} schema_cls = Schema.from_dict( {"ids": fields.DelimitedTuple((fields.Int, fields.Int), delimiter="|")} ) schema = schema_cls() parsed = parser.parse(schema, web_request) assert parsed["ids"] == (1, 2) data = schema.dump(parsed) assert data["ids"] == "1|2" def test_delimited_list_load_list_errors(web_request, parser): web_request.json = {"ids": [1, 2, 3]} schema_cls = Schema.from_dict({"ids": fields.DelimitedList(fields.Int())}) schema = schema_cls() with pytest.raises(ValidationError) as excinfo: parser.parse(schema, web_request) exc = excinfo.value assert isinstance(exc, ValidationError) errors = exc.args[0] assert errors["ids"] == ["Not a valid delimited list."] def test_delimited_tuple_load_list_errors(web_request, parser): web_request.json = {"ids": [1, 2]} schema_cls = Schema.from_dict( {"ids": fields.DelimitedTuple((fields.Int, fields.Int))} ) schema = schema_cls() with pytest.raises(ValidationError) as excinfo: parser.parse(schema, web_request) exc = excinfo.value assert isinstance(exc, ValidationError) errors = exc.args[0] assert errors["ids"] == ["Not a valid delimited tuple."] # Regresion test for https://github.com/marshmallow-code/webargs/issues/149 def test_delimited_list_passed_invalid_type(web_request, parser): web_request.json = {"ids": 1} schema_cls = Schema.from_dict({"ids": fields.DelimitedList(fields.Int())}) schema = schema_cls() with pytest.raises(ValidationError) as excinfo: parser.parse(schema, web_request) assert excinfo.value.messages == {"json": {"ids": ["Not a valid delimited list."]}} def test_delimited_tuple_passed_invalid_type(web_request, parser): web_request.json = {"ids": 1} schema_cls = Schema.from_dict({"ids": fields.DelimitedTuple((fields.Int,))}) schema = schema_cls() with pytest.raises(ValidationError) as excinfo: parser.parse(schema, web_request) assert excinfo.value.messages == {"json": {"ids": ["Not a valid delimited tuple."]}} def test_missing_list_argument_not_in_parsed_result(web_request, parser): # arg missing in request web_request.json = {} args = {"ids": fields.List(fields.Int())} result = parser.parse(args, web_request) assert "ids" not in result def test_type_conversion_with_multiple_required(web_request, parser): web_request.json = {} args = {"ids": fields.List(fields.Int(), required=True)} msg = "Missing data for required field." with pytest.raises(ValidationError, match=msg): parser.parse(args, web_request) @pytest.mark.parametrize("input_dict", multidicts) @pytest.mark.parametrize( "setting", [ "is_multiple_true", "is_multiple_false", "is_multiple_notset", "list_field", "tuple_field", "added_to_known", ], ) def test_is_multiple_detection(web_request, parser, input_dict, setting): # this custom class "multiplexes" in that it can be given a single value or # list of values -- a single value is treated as a string, and a list of # values is treated as a list of strings class CustomMultiplexingField(fields.String): def _deserialize(self, value, attr, data, **kwargs): if isinstance(value, str): return super()._deserialize(value, attr, data, **kwargs) return [ self._deserialize(v, attr, data, **kwargs) for v in value if isinstance(v, str) ] def _serialize(self, value, attr, **kwargs): if isinstance(value, str): return super()._serialize(value, attr, **kwargs) return [ self._serialize(v, attr, **kwargs) for v in value if isinstance(v, str) ] class CustomMultipleField(CustomMultiplexingField): is_multiple = True class CustomNonMultipleField(CustomMultiplexingField): is_multiple = False # the request's query params are the input multidict web_request.query = input_dict # case 1: is_multiple=True if setting == "is_multiple_true": # the multidict should unpack to a list of strings # # order is not necessarily guaranteed by the multidict implementations, but # both values must be present args = {"foos": CustomMultipleField()} result = parser.parse(args, web_request, location="query") assert result["foos"] in (["a", "b"], ["b", "a"]) # case 2: is_multiple=False elif setting == "is_multiple_false": # the multidict should unpack to a string # # either value may be returned, depending on the multidict implementation, # but not both args = {"foos": CustomNonMultipleField()} result = parser.parse(args, web_request, location="query") assert result["foos"] in ("a", "b") # case 3: is_multiple is not set elif setting == "is_multiple_notset": # this should be the same as is_multiple=False args = {"foos": CustomMultiplexingField()} result = parser.parse(args, web_request, location="query") assert result["foos"] in ("a", "b") # case 4: the field is a List (special case) elif setting == "list_field": # this should behave like the is_multiple=True case args = {"foos": fields.List(fields.Str())} result = parser.parse(args, web_request, location="query") assert result["foos"] in (["a", "b"], ["b", "a"]) # case 5: the field is a Tuple (special case) elif setting == "tuple_field": # this should behave like the is_multiple=True case and produce a tuple args = {"foos": fields.Tuple((fields.Str, fields.Str))} result = parser.parse(args, web_request, location="query") assert result["foos"] in (("a", "b"), ("b", "a")) # case 6: the field is custom, but added to the known fields of the parser elif setting == "added_to_known": # if it's included in the known multifields and is_multiple is not set, behave # like is_multiple=True parser.KNOWN_MULTI_FIELDS.append(CustomMultiplexingField) args = {"foos": CustomMultiplexingField()} result = parser.parse(args, web_request, location="query") assert result["foos"] in (["a", "b"], ["b", "a"]) else: raise NotImplementedError def test_validation_errors_in_validator_are_passed_to_handle_error(parser, web_request): def validate(value): raise ValidationError("Something went wrong.") args = {"name": fields.Field(validate=validate, location="json")} web_request.json = {"name": "invalid"} with pytest.raises(ValidationError) as excinfo: parser.parse(args, web_request) exc = excinfo.value assert isinstance(exc, ValidationError) errors = exc.args[0] assert errors["name"] == ["Something went wrong."] def test_parse_basic(web_request, parser): web_request.json = {"foo": "42"} args = {"foo": fields.Int()} result = parser.parse(args, web_request) assert result == {"foo": 42} def test_parse_raises_validation_error_if_data_invalid(web_request, parser): args = {"email": fields.Email()} web_request.json = {"email": "invalid"} with pytest.raises(ValidationError): parser.parse(args, web_request) def test_nested_field_from_dict(): # webargs.fields.Nested implements dict handling argmap = {"nest": fields.Nested({"foo": fields.Field()})} schema_cls = Schema.from_dict(argmap) assert issubclass(schema_cls, Schema) schema = schema_cls() assert "nest" in schema.fields assert type(schema.fields["nest"]) is fields.Nested assert "foo" in schema.fields["nest"].schema.fields def test_is_json(): assert is_json(None) is False assert is_json("application/json") is True assert is_json("application/xml") is False assert is_json("application/vnd.api+json") is True def test_get_mimetype(): assert get_mimetype("application/json") == "application/json" assert get_mimetype("application/json;charset=utf8") == "application/json" class MockRequestParserWithErrorHandler(MockRequestParser): def handle_error(self, error, req, schema, *, error_status_code, error_headers): assert isinstance(error, ValidationError) assert isinstance(schema, Schema) raise MockHTTPError(error_status_code, error_headers) def test_parse_with_error_status_code_and_headers(web_request): parser = MockRequestParserWithErrorHandler() web_request.json = {"foo": 42} args = {"foo": fields.Field(validate=lambda x: False)} with pytest.raises(MockHTTPError) as excinfo: parser.parse( args, web_request, error_status_code=418, error_headers={"X-Foo": "bar"} ) error = excinfo.value assert error.status_code == 418 assert error.headers == {"X-Foo": "bar"} @mock.patch("webargs.core.Parser.load_json") def test_custom_schema_class(load_json, web_request): class CustomSchema(Schema): @pre_load def pre_load(self, data, **kwargs): data["value"] += " world" return data load_json.return_value = {"value": "hello"} argmap = {"value": fields.Str()} p = Parser(schema_class=CustomSchema) ret = p.parse(argmap, web_request) assert ret == {"value": "hello world"} @mock.patch("webargs.core.Parser.load_json") def test_custom_default_schema_class(load_json, web_request): class CustomSchema(Schema): @pre_load def pre_load(self, data, **kwargs): data["value"] += " world" return data class CustomParser(Parser): DEFAULT_SCHEMA_CLASS = CustomSchema load_json.return_value = {"value": "hello"} argmap = {"value": fields.Str()} p = CustomParser() ret = p.parse(argmap, web_request) assert ret == {"value": "hello world"} def test_parser_pre_load(web_request): class CustomParser(MockRequestParser): # pre-load hook to strip whitespace from query params def pre_load(self, data, *, schema, req, location): if location == "query": return {k: v.strip() for k, v in data.items()} return data parser = CustomParser() # mock data for both query and json web_request.query = web_request.json = {"value": " hello "} argmap = {"value": fields.Str()} # data gets through for 'json' just fine ret = parser.parse(argmap, web_request) assert ret == {"value": " hello "} # but for 'query', the pre_load hook changes things ret = parser.parse(argmap, web_request, location="query") assert ret == {"value": "hello"} # this test is meant to be a run of the WhitspaceStrippingFlaskParser we give # in the docs/advanced.rst examples for how to use pre_load # this helps ensure that the example code is correct # rather than a FlaskParser, we're working with the mock parser, but it's # otherwise the same def test_whitespace_stripping_parser_example(web_request): def _strip_whitespace(value): if isinstance(value, str): value = value.strip() elif isinstance(value, typing.Mapping): return {k: _strip_whitespace(value[k]) for k in value} elif isinstance(value, (list, tuple)): return type(value)(map(_strip_whitespace, value)) return value class WhitspaceStrippingParser(MockRequestParser): def pre_load(self, location_data, *, schema, req, location): if location in ("query", "form"): ret = _strip_whitespace(location_data) return ret return location_data parser = WhitspaceStrippingParser() # mock data for query, form, and json web_request.form = web_request.query = web_request.json = {"value": " hello "} argmap = {"value": fields.Str()} # data gets through for 'json' just fine ret = parser.parse(argmap, web_request) assert ret == {"value": " hello "} # but for 'query' and 'form', the pre_load hook changes things for loc in ("query", "form"): ret = parser.parse(argmap, web_request, location=loc) assert ret == {"value": "hello"} # check that it applies in the case where the field is a list type # applied to an argument (logic for `tuple` is effectively the same) web_request.form = web_request.query = web_request.json = { "ids": [" 1", "3", " 4"], "values": [" foo ", " bar"], } schema = Schema.from_dict( {"ids": fields.List(fields.Int), "values": fields.List(fields.Str)} ) for loc in ("query", "form"): ret = parser.parse(schema, web_request, location=loc) assert ret == {"ids": [1, 3, 4], "values": ["foo", "bar"]} # json loading should also work even though the pre_load hook above # doesn't strip whitespace from JSON data # - values=[" foo ", ...] will have whitespace preserved # - ids=[" 1", ...] will still parse okay because " 1" is valid for fields.Int ret = parser.parse(schema, web_request, location="json") assert ret == {"ids": [1, 3, 4], "values": [" foo ", " bar"]} python-webargs_8.0.1.orig/tests/test_djangoparser.py0000644000000000000000000000205713775063555017716 0ustar00import pytest from tests.apps.django_app.base.wsgi import application from webargs.testing import CommonTestCase class TestDjangoParser(CommonTestCase): def create_app(self): return application @pytest.mark.skip( reason="skipping because DjangoParser does not implement handle_error" ) def test_use_args_with_validation(self): pass def test_parsing_in_class_based_view(self, testapp): assert testapp.get("/echo_cbv?name=Fred").json == {"name": "Fred"} assert testapp.post_json("/echo_cbv", {"name": "Fred"}).json == {"name": "Fred"} def test_use_args_in_class_based_view(self, testapp): res = testapp.get("/echo_use_args_cbv?name=Fred") assert res.json == {"name": "Fred"} res = testapp.post_json("/echo_use_args_cbv", {"name": "Fred"}) assert res.json == {"name": "Fred"} def test_use_args_in_class_based_view_with_path_param(self, testapp): res = testapp.get("/echo_use_args_with_path_param_cbv/42?name=Fred") assert res.json == {"name": "Fred"} python-webargs_8.0.1.orig/tests/test_falconparser.py0000644000000000000000000000517613775063555017723 0ustar00import pytest import falcon.testing from webargs.testing import CommonTestCase from tests.apps.falcon_app import create_app class TestFalconParser(CommonTestCase): def create_app(self): return create_app() @pytest.mark.skip(reason="files location not supported for falconparser") def test_parse_files(self, testapp): pass def test_use_args_hook(self, testapp): assert testapp.get("/echo_use_args_hook?name=Fred").json == {"name": "Fred"} def test_parse_media(self, testapp): assert testapp.post_json("/echo_media", {"name": "Fred"}).json == { "name": "Fred" } def test_parse_media_missing(self, testapp): assert testapp.post("/echo_media", "").json == {"name": "World"} def test_parse_media_empty(self, testapp): assert testapp.post_json("/echo_media", {}).json == {"name": "World"} def test_parse_media_error_unexpected_int(self, testapp): res = testapp.post_json("/echo_media", 1, expect_errors=True) assert res.status_code == 422 # https://github.com/marshmallow-code/webargs/issues/427 @pytest.mark.parametrize("path", ["/echo_json", "/echo_media"]) def test_parse_json_with_nonutf8_chars(self, testapp, path): res = testapp.post( path, b"\xfe", headers={"Accept": "application/json", "Content-Type": "application/json"}, expect_errors=True, ) assert res.status_code == 400 if path.endswith("json"): assert res.json["errors"] == {"json": ["Invalid JSON body."]} # https://github.com/sloria/webargs/issues/329 @pytest.mark.parametrize("path", ["/echo_json", "/echo_media"]) def test_invalid_json(self, testapp, path): res = testapp.post( path, '{"foo": "bar", }', headers={"Accept": "application/json", "Content-Type": "application/json"}, expect_errors=True, ) assert res.status_code == 400 if path.endswith("json"): assert res.json["errors"] == {"json": ["Invalid JSON body."]} # Falcon converts headers to all-caps def test_parsing_headers(self, testapp): res = testapp.get("/echo_headers", headers={"name": "Fred"}) assert res.json == {"NAME": "Fred"} # `falcon.testing.TestClient.simulate_request` parses request with `wsgiref` def test_body_parsing_works_with_simulate(self): app = self.create_app() client = falcon.testing.TestClient(app) res = client.simulate_post( "/echo_json", json={"name": "Fred"}, ) assert res.json == {"name": "Fred"} python-webargs_8.0.1.orig/tests/test_flaskparser.py0000644000000000000000000001104113775063555017545 0ustar00from unittest import mock from werkzeug.exceptions import HTTPException, BadRequest import pytest from marshmallow import Schema from flask import Flask from webargs import fields, ValidationError, missing from webargs.flaskparser import parser, abort from webargs.core import json from .apps.flask_app import app from webargs.testing import CommonTestCase class TestFlaskParser(CommonTestCase): def create_app(self): return app def test_parsing_view_args(self, testapp): res = testapp.get("/echo_view_arg/42") assert res.json == {"view_arg": 42} def test_parsing_invalid_view_arg(self, testapp): res = testapp.get("/echo_view_arg/foo", expect_errors=True) assert res.status_code == 422 assert res.json == {"view_args": {"view_arg": ["Not a valid integer."]}} def test_use_args_with_view_args_parsing(self, testapp): res = testapp.get("/echo_view_arg_use_args/42") assert res.json == {"view_arg": 42} def test_use_args_on_a_method_view(self, testapp): res = testapp.post_json("/echo_method_view_use_args", {"val": 42}) assert res.json == {"val": 42} def test_use_kwargs_on_a_method_view(self, testapp): res = testapp.post_json("/echo_method_view_use_kwargs", {"val": 42}) assert res.json == {"val": 42} def test_use_kwargs_with_missing_data(self, testapp): res = testapp.post_json("/echo_use_kwargs_missing", {"username": "foo"}) assert res.json == {"username": "foo"} # regression test for https://github.com/marshmallow-code/webargs/issues/145 def test_nested_many_with_data_key(self, testapp): post_with_raw_fieldname_args = ( "/echo_nested_many_data_key", {"x_field": [{"id": 42}]}, ) res = testapp.post_json(*post_with_raw_fieldname_args, expect_errors=True) assert res.status_code == 422 res = testapp.post_json("/echo_nested_many_data_key", {"X-Field": [{"id": 24}]}) assert res.json == {"x_field": [{"id": 24}]} res = testapp.post_json("/echo_nested_many_data_key", {}) assert res.json == {} # regression test for # https://github.com/marshmallow-code/webargs/issues/500 def test_parsing_unexpected_headers_when_raising(self, testapp): res = testapp.get( "/echo_headers_raising", expect_errors=True, headers={"X-Unexpected": "foo"} ) assert res.status_code == 422 assert "headers" in res.json assert "X-Unexpected" in set(res.json["headers"].keys()) @mock.patch("webargs.flaskparser.abort") def test_abort_called_on_validation_error(mock_abort): # error handling must raise an error to be valid mock_abort.side_effect = BadRequest("foo") app = Flask("testapp") def validate(x): return x == 42 argmap = {"value": fields.Field(validate=validate)} with app.test_request_context( "/foo", method="post", data=json.dumps({"value": 41}), content_type="application/json", ): with pytest.raises(HTTPException): parser.parse(argmap) mock_abort.assert_called() abort_args, abort_kwargs = mock_abort.call_args assert abort_args[0] == 422 expected_msg = "Invalid value." assert abort_kwargs["messages"]["json"]["value"] == [expected_msg] assert type(abort_kwargs["exc"]) == ValidationError @pytest.mark.parametrize("mimetype", [None, "application/json"]) def test_load_json_returns_missing_if_no_data(mimetype): req = mock.Mock() req.mimetype = mimetype req.get_data.return_value = "" schema = Schema.from_dict({"foo": fields.Field()})() assert parser.load_json(req, schema) is missing def test_abort_with_message(): with pytest.raises(HTTPException) as excinfo: abort(400, message="custom error message") assert excinfo.value.data["message"] == "custom error message" def test_abort_has_serializable_data(): with pytest.raises(HTTPException) as excinfo: abort(400, message="custom error message") serialized_error = json.dumps(excinfo.value.data) error = json.loads(serialized_error) assert isinstance(error, dict) assert error["message"] == "custom error message" with pytest.raises(HTTPException) as excinfo: abort( 400, message="custom error message", exc=ValidationError("custom error message"), ) serialized_error = json.dumps(excinfo.value.data) error = json.loads(serialized_error) assert isinstance(error, dict) assert error["message"] == "custom error message" python-webargs_8.0.1.orig/tests/test_pyramidparser.py0000644000000000000000000000067313775063555020123 0ustar00from webargs.testing import CommonTestCase class TestPyramidParser(CommonTestCase): def create_app(self): from .apps.pyramid_app import create_app return create_app() def test_use_args_with_callable_view(self, testapp): assert testapp.get("/echo_callable?value=42").json == {"value": 42} def test_parse_matchdict(self, testapp): assert testapp.get("/echo_matchdict/42").json == {"mymatch": 42} python-webargs_8.0.1.orig/tests/test_tornadoparser.py0000644000000000000000000004313213775063555020121 0ustar00from unittest import mock from urllib.parse import urlencode import marshmallow as ma import pytest import tornado.concurrent import tornado.http1connection import tornado.httpserver import tornado.httputil import tornado.ioloop import tornado.web from tornado.testing import AsyncHTTPTestCase from webargs import fields, missing from webargs.core import json, parse_json from webargs.tornadoparser import ( WebArgsTornadoMultiDictProxy, parser, use_args, use_kwargs, ) name = "name" value = "value" class AuthorSchema(ma.Schema): name = fields.Str(missing="World", validate=lambda n: len(n) >= 3) works = fields.List(fields.Str()) author_schema = AuthorSchema() def test_tornado_multidictproxy(): for dictval, fieldname, expected in ( ({"name": "Sophocles"}, "name", "Sophocles"), ({"name": "Sophocles"}, "works", missing), ({"works": ["Antigone", "Oedipus Rex"]}, "works", ["Antigone", "Oedipus Rex"]), ({"works": ["Antigone", "Oedipus at Colonus"]}, "name", missing), ): proxy = WebArgsTornadoMultiDictProxy(dictval, author_schema) assert proxy.get(fieldname) == expected class TestQueryArgs: def test_it_should_get_single_values(self): query = [("name", "Aeschylus")] request = make_get_request(query) result = parser.load_querystring(request, author_schema) assert result["name"] == "Aeschylus" def test_it_should_get_multiple_values(self): query = [("works", "Agamemnon"), ("works", "Nereids")] request = make_get_request(query) result = parser.load_querystring(request, author_schema) assert result["works"] == ["Agamemnon", "Nereids"] def test_it_should_return_missing_if_not_present(self): query = [] request = make_get_request(query) result = parser.load_querystring(request, author_schema) assert result["name"] is missing assert result["works"] is missing class TestFormArgs: def test_it_should_get_single_values(self): query = [("name", "Aristophanes")] request = make_form_request(query) result = parser.load_form(request, author_schema) assert result["name"] == "Aristophanes" def test_it_should_get_multiple_values(self): query = [("works", "The Wasps"), ("works", "The Frogs")] request = make_form_request(query) result = parser.load_form(request, author_schema) assert result["works"] == ["The Wasps", "The Frogs"] def test_it_should_return_missing_if_not_present(self): query = [] request = make_form_request(query) result = parser.load_form(request, author_schema) assert result["name"] is missing assert result["works"] is missing class TestJSONArgs: def test_it_should_get_single_values(self): query = {"name": "Euripides"} request = make_json_request(query) result = parser.load_json(request, author_schema) assert result["name"] == "Euripides" def test_parsing_request_with_vendor_content_type(self): query = {"name": "Euripides"} request = make_json_request( query, content_type="application/vnd.api+json; charset=UTF-8" ) result = parser.load_json(request, author_schema) assert result["name"] == "Euripides" def test_it_should_get_multiple_values(self): query = {"works": ["Medea", "Electra"]} request = make_json_request(query) result = parser.load_json(request, author_schema) assert result["works"] == ["Medea", "Electra"] def test_it_should_get_multiple_nested_values(self): class CustomSchema(ma.Schema): works = fields.List( fields.Nested({"author": fields.Str(), "workname": fields.Str()}) ) custom_schema = CustomSchema() query = { "works": [ {"author": "Euripides", "workname": "Hecuba"}, {"author": "Aristophanes", "workname": "The Birds"}, ] } request = make_json_request(query) result = parser.load_json(request, custom_schema) assert result["works"] == [ {"author": "Euripides", "workname": "Hecuba"}, {"author": "Aristophanes", "workname": "The Birds"}, ] def test_it_should_not_include_fieldnames_if_not_present(self): query = {} request = make_json_request(query) result = parser.load_json(request, author_schema) assert result == {} def test_it_should_handle_type_error_on_load_json(self, loop): # but this is different from the test above where the payload was valid # and empty -- missing vs {} # NOTE: `loop` is the pytest-aiohttp event loop fixture, but it's # important to get an event loop here so that we can construct a future request = make_request( body=tornado.concurrent.Future(), headers={"Content-Type": "application/json"}, ) result = parser.load_json(request, author_schema) assert result is missing def test_it_should_handle_value_error_on_parse_json(self): request = make_request("this is json not") result = parser.load_json(request, author_schema) assert result is missing class TestHeadersArgs: def test_it_should_get_single_values(self): query = {"name": "Euphorion"} request = make_request(headers=query) result = parser.load_headers(request, author_schema) assert result["name"] == "Euphorion" def test_it_should_get_multiple_values(self): query = {"works": ["Prometheus Bound", "Prometheus Unbound"]} request = make_request(headers=query) result = parser.load_headers(request, author_schema) assert result["works"] == ["Prometheus Bound", "Prometheus Unbound"] def test_it_should_return_missing_if_not_present(self): request = make_request() result = parser.load_headers(request, author_schema) assert result["name"] is missing assert result["works"] is missing class TestFilesArgs: def test_it_should_get_single_values(self): query = [("name", "Sappho")] request = make_files_request(query) result = parser.load_files(request, author_schema) assert result["name"] == "Sappho" def test_it_should_get_multiple_values(self): query = [("works", "Sappho 31"), ("works", "Ode to Aphrodite")] request = make_files_request(query) result = parser.load_files(request, author_schema) assert result["works"] == ["Sappho 31", "Ode to Aphrodite"] def test_it_should_return_missing_if_not_present(self): query = [] request = make_files_request(query) result = parser.load_files(request, author_schema) assert result["name"] is missing assert result["works"] is missing class TestErrorHandler: def test_it_should_raise_httperror_on_failed_validation(self): args = {"foo": fields.Field(validate=lambda x: False)} with pytest.raises(tornado.web.HTTPError): parser.parse(args, make_json_request({"foo": 42})) class TestParse: def test_it_should_parse_query_arguments(self): attrs = {"string": fields.Field(), "integer": fields.List(fields.Int())} request = make_get_request( [("string", "value"), ("integer", "1"), ("integer", "2")] ) parsed = parser.parse(attrs, request, location="query") assert parsed["integer"] == [1, 2] assert parsed["string"] == value def test_it_should_parse_form_arguments(self): attrs = {"string": fields.Field(), "integer": fields.List(fields.Int())} request = make_form_request( [("string", "value"), ("integer", "1"), ("integer", "2")] ) parsed = parser.parse(attrs, request, location="form") assert parsed["integer"] == [1, 2] assert parsed["string"] == value def test_it_should_parse_json_arguments(self): attrs = {"string": fields.Str(), "integer": fields.List(fields.Int())} request = make_json_request({"string": "value", "integer": [1, 2]}) parsed = parser.parse(attrs, request) assert parsed["integer"] == [1, 2] assert parsed["string"] == value def test_it_should_raise_when_json_is_invalid(self): attrs = {"foo": fields.Str()} request = make_request( body='{"foo": 42,}', headers={"Content-Type": "application/json"} ) with pytest.raises(tornado.web.HTTPError) as excinfo: parser.parse(attrs, request) error = excinfo.value assert error.status_code == 400 assert error.messages == {"json": ["Invalid JSON body."]} def test_it_should_parse_header_arguments(self): attrs = {"string": fields.Str(), "integer": fields.List(fields.Int())} request = make_request(headers={"string": "value", "integer": ["1", "2"]}) parsed = parser.parse(attrs, request, location="headers") assert parsed["string"] == value assert parsed["integer"] == [1, 2] def test_it_should_parse_cookies_arguments(self): attrs = {"string": fields.Str(), "integer": fields.List(fields.Int())} request = make_cookie_request( [("string", "value"), ("integer", "1"), ("integer", "2")] ) parsed = parser.parse(attrs, request, location="cookies") assert parsed["string"] == value assert parsed["integer"] == [2] def test_it_should_parse_files_arguments(self): attrs = {"string": fields.Str(), "integer": fields.List(fields.Int())} request = make_files_request( [("string", "value"), ("integer", "1"), ("integer", "2")] ) parsed = parser.parse(attrs, request, location="files") assert parsed["string"] == value assert parsed["integer"] == [1, 2] def test_it_should_parse_required_arguments(self): args = {"foo": fields.Field(required=True)} request = make_json_request({}) msg = "Missing data for required field." with pytest.raises(tornado.web.HTTPError, match=msg): parser.parse(args, request) def test_it_should_parse_multiple_arg_required(self): args = {"foo": fields.List(fields.Int(), required=True)} request = make_json_request({}) msg = "Missing data for required field." with pytest.raises(tornado.web.HTTPError, match=msg): parser.parse(args, request) class TestUseArgs: def test_it_should_pass_parsed_as_first_argument(self): class Handler: request = make_json_request({"key": "value"}) @use_args({"key": fields.Field()}) def get(self, *args, **kwargs): assert args[0] == {"key": "value"} assert kwargs == {} return True handler = Handler() result = handler.get() assert result is True def test_it_should_pass_parsed_as_kwargs_arguments(self): class Handler: request = make_json_request({"key": "value"}) @use_kwargs({"key": fields.Field()}) def get(self, *args, **kwargs): assert args == () assert kwargs == {"key": "value"} return True handler = Handler() result = handler.get() assert result is True def test_it_should_be_validate_arguments_when_validator_is_passed(self): class Handler: request = make_json_request({"foo": 41}) @use_kwargs({"foo": fields.Int()}, validate=lambda args: args["foo"] > 42) def get(self, args): return True handler = Handler() with pytest.raises(tornado.web.HTTPError): handler.get() def make_uri(args): return "/test?" + urlencode(args) def make_form_body(args): return urlencode(args) def make_json_body(args): return json.dumps(args) def make_get_request(args): return make_request(uri=make_uri(args)) def make_form_request(args): return make_request( body=make_form_body(args), headers={"Content-Type": "application/x-www-form-urlencoded"}, ) def make_json_request(args, content_type="application/json; charset=UTF-8"): return make_request( body=make_json_body(args), headers={"Content-Type": content_type} ) def make_cookie_request(args): return make_request(headers={"Cookie": " ;".join("=".join(pair) for pair in args)}) def make_files_request(args): files = {} for key, value in args: if isinstance(value, list): files.setdefault(key, []).extend(value) else: files.setdefault(key, []).append(value) return make_request(files=files) def make_request(uri=None, body=None, headers=None, files=None): uri = uri if uri is not None else "" body = body if body is not None else "" method = "POST" if body else "GET" # Need to make a mock connection right now because Tornado 4.0 requires a # remote_ip in the context attribute. 4.1 addresses this, and this # will be unnecessary once it is released # https://github.com/tornadoweb/tornado/issues/1118 mock_connection = mock.Mock(spec=tornado.http1connection.HTTP1Connection) mock_connection.context = mock.Mock() mock_connection.remote_ip = None content_type = headers.get("Content-Type", "") if headers else "" request = tornado.httputil.HTTPServerRequest( method=method, uri=uri, body=body, headers=headers, files=files, connection=mock_connection, ) tornado.httputil.parse_body_arguments( content_type=content_type, body=body.encode("latin-1") if hasattr(body, "encode") else body, arguments=request.body_arguments, files=request.files, ) return request class EchoHandler(tornado.web.RequestHandler): ARGS = {"name": fields.Str()} @use_args(ARGS, location="query") def get(self, args): self.write(args) class EchoFormHandler(tornado.web.RequestHandler): ARGS = {"name": fields.Str()} @use_args(ARGS, location="form") def post(self, args): self.write(args) class EchoJSONHandler(tornado.web.RequestHandler): ARGS = {"name": fields.Str()} @use_args(ARGS) def post(self, args): self.write(args) class EchoWithParamHandler(tornado.web.RequestHandler): ARGS = {"name": fields.Str()} @use_args(ARGS, location="query") def get(self, id, args): self.write(args) echo_app = tornado.web.Application( [ (r"/echo", EchoHandler), (r"/echo_form", EchoFormHandler), (r"/echo_json", EchoJSONHandler), (r"/echo_with_param/(\d+)", EchoWithParamHandler), ] ) class TestApp(AsyncHTTPTestCase): def get_app(self): return echo_app def test_post(self): res = self.fetch( "/echo_json", method="POST", headers={"Content-Type": "application/json"}, body=json.dumps({"name": "Steve"}), ) json_body = parse_json(res.body) assert json_body["name"] == "Steve" res = self.fetch( "/echo_json", method="POST", headers={"Content-Type": "application/json"}, body=json.dumps({}), ) json_body = parse_json(res.body) assert "name" not in json_body def test_get_with_no_json_body(self): res = self.fetch( "/echo", method="GET", headers={"Content-Type": "application/json"} ) json_body = parse_json(res.body) assert "name" not in json_body def test_get_path_param(self): res = self.fetch( "/echo_with_param/42?name=Steve", method="GET", headers={"Content-Type": "application/json"}, ) json_body = parse_json(res.body) assert json_body == {"name": "Steve"} class ValidateHandler(tornado.web.RequestHandler): ARGS = {"name": fields.Str(required=True)} @use_args(ARGS) def post(self, args): self.write(args) @use_kwargs(ARGS, location="query") def get(self, name): self.write({"status": "success"}) def always_fail(val): raise ma.ValidationError("something went wrong") class AlwaysFailHandler(tornado.web.RequestHandler): ARGS = {"name": fields.Str(validate=always_fail)} @use_args(ARGS) def post(self, args): self.write(args) validate_app = tornado.web.Application( [(r"/echo", ValidateHandler), (r"/alwaysfail", AlwaysFailHandler)] ) class TestValidateApp(AsyncHTTPTestCase): def get_app(self): return validate_app def test_required_field_provided(self): res = self.fetch( "/echo", method="POST", headers={"Content-Type": "application/json"}, body=json.dumps({"name": "johnny"}), ) json_body = parse_json(res.body) assert json_body["name"] == "johnny" def test_missing_required_field_throws_422(self): res = self.fetch( "/echo", method="POST", headers={"Content-Type": "application/json"}, body=json.dumps({"occupation": "pizza"}), ) assert res.code == 422 def test_user_validator_returns_422_by_default(self): res = self.fetch( "/alwaysfail", method="POST", headers={"Content-Type": "application/json"}, body=json.dumps({"name": "Steve"}), ) assert res.code == 422 def test_use_kwargs_with_error(self): res = self.fetch("/echo", method="GET") assert res.code == 422 if __name__ == "__main__": echo_app.listen(8888) tornado.ioloop.IOLoop.instance().start() python-webargs_8.0.1.orig/tests/apps/__init__.py0000644000000000000000000000000013326636200016645 0ustar00python-webargs_8.0.1.orig/tests/apps/aiohttp_app.py0000644000000000000000000001630613775063555017455 0ustar00import aiohttp from aiohttp.web import json_response import marshmallow as ma from webargs import fields from webargs.aiohttpparser import parser, use_args, use_kwargs from webargs.core import json hello_args = {"name": fields.Str(missing="World", validate=lambda n: len(n) >= 3)} hello_multiple = {"name": fields.List(fields.Str())} class HelloSchema(ma.Schema): name = fields.Str(missing="World", validate=lambda n: len(n) >= 3) hello_many_schema = HelloSchema(many=True) # variant which ignores unknown fields hello_exclude_schema = HelloSchema(unknown=ma.EXCLUDE) ##### Handlers ##### async def echo(request): parsed = await parser.parse(hello_args, request, location="query") return json_response(parsed) async def echo_form(request): parsed = await parser.parse(hello_args, request, location="form") return json_response(parsed) async def echo_json(request): try: parsed = await parser.parse(hello_args, request, location="json") except json.JSONDecodeError: raise aiohttp.web.HTTPBadRequest( body=json.dumps(["Invalid JSON."]).encode("utf-8"), content_type="application/json", ) return json_response(parsed) async def echo_json_or_form(request): try: parsed = await parser.parse(hello_args, request, location="json_or_form") except json.JSONDecodeError: raise aiohttp.web.HTTPBadRequest( body=json.dumps(["Invalid JSON."]).encode("utf-8"), content_type="application/json", ) return json_response(parsed) @use_args(hello_args, location="query") async def echo_use_args(request, args): return json_response(args) @use_kwargs(hello_args, location="query") async def echo_use_kwargs(request, name): return json_response({"name": name}) @use_args( {"value": fields.Int()}, validate=lambda args: args["value"] > 42, location="form" ) async def echo_use_args_validated(request, args): return json_response(args) async def echo_ignoring_extra_data(request): return json_response( await parser.parse(hello_exclude_schema, request, unknown=None) ) async def echo_multi(request): parsed = await parser.parse(hello_multiple, request, location="query") return json_response(parsed) async def echo_multi_form(request): parsed = await parser.parse(hello_multiple, request, location="form") return json_response(parsed) async def echo_multi_json(request): parsed = await parser.parse(hello_multiple, request) return json_response(parsed) async def echo_many_schema(request): parsed = await parser.parse(hello_many_schema, request) return json_response(parsed) @use_args({"value": fields.Int()}, location="query") async def echo_use_args_with_path_param(request, args): return json_response(args) @use_kwargs({"value": fields.Int()}, location="query") async def echo_use_kwargs_with_path_param(request, value): return json_response({"value": value}) @use_args({"page": fields.Int(), "q": fields.Int()}, location="query") @use_args({"name": fields.Str()}) async def echo_use_args_multiple(request, query_parsed, json_parsed): return json_response({"query_parsed": query_parsed, "json_parsed": json_parsed}) async def always_error(request): def always_fail(value): raise ma.ValidationError("something went wrong") args = {"text": fields.Str(validate=always_fail)} parsed = await parser.parse(args, request) return json_response(parsed) async def echo_headers(request): parsed = await parser.parse(hello_args, request, location="headers") return json_response(parsed) async def echo_cookie(request): parsed = await parser.parse(hello_args, request, location="cookies") return json_response(parsed) async def echo_nested(request): args = {"name": fields.Nested({"first": fields.Str(), "last": fields.Str()})} parsed = await parser.parse(args, request) return json_response(parsed) async def echo_multiple_args(request): args = {"first": fields.Str(), "last": fields.Str()} parsed = await parser.parse(args, request) return json_response(parsed) async def echo_nested_many(request): args = { "users": fields.Nested({"id": fields.Int(), "name": fields.Str()}, many=True) } parsed = await parser.parse(args, request) return json_response(parsed) async def echo_nested_many_data_key(request): args = { "x_field": fields.Nested({"id": fields.Int()}, many=True, data_key="X-Field") } parsed = await parser.parse(args, request) return json_response(parsed) async def echo_match_info(request): parsed = await parser.parse( {"mymatch": fields.Int()}, request, location="match_info" ) return json_response(parsed) class EchoHandler: @use_args(hello_args, location="query") async def get(self, request, args): return json_response(args) class EchoHandlerView(aiohttp.web.View): @use_args(hello_args, location="query") async def get(self, args): return json_response(args) @use_args(HelloSchema, as_kwargs=True, location="query") async def echo_use_schema_as_kwargs(request, name): return json_response({"name": name}) ##### App factory ##### def add_route(app, methods, route, handler): for method in methods: app.router.add_route(method, route, handler) def create_app(): app = aiohttp.web.Application() add_route(app, ["GET"], "/echo", echo) add_route(app, ["POST"], "/echo_form", echo_form) add_route(app, ["POST"], "/echo_json", echo_json) add_route(app, ["POST"], "/echo_json_or_form", echo_json_or_form) add_route(app, ["GET"], "/echo_use_args", echo_use_args) add_route(app, ["GET"], "/echo_use_kwargs", echo_use_kwargs) add_route(app, ["POST"], "/echo_use_args_validated", echo_use_args_validated) add_route(app, ["POST"], "/echo_ignoring_extra_data", echo_ignoring_extra_data) add_route(app, ["GET"], "/echo_multi", echo_multi) add_route(app, ["POST"], "/echo_multi_form", echo_multi_form) add_route(app, ["POST"], "/echo_multi_json", echo_multi_json) add_route(app, ["GET", "POST"], "/echo_many_schema", echo_many_schema) add_route( app, ["GET", "POST"], "/echo_use_args_with_path_param/{name}", echo_use_args_with_path_param, ) add_route( app, ["GET", "POST"], "/echo_use_kwargs_with_path_param/{name}", echo_use_kwargs_with_path_param, ) add_route(app, ["POST"], "/echo_use_args_multiple", echo_use_args_multiple) add_route(app, ["GET", "POST"], "/error", always_error) add_route(app, ["GET"], "/echo_headers", echo_headers) add_route(app, ["GET"], "/echo_cookie", echo_cookie) add_route(app, ["POST"], "/echo_nested", echo_nested) add_route(app, ["POST"], "/echo_multiple_args", echo_multiple_args) add_route(app, ["POST"], "/echo_nested_many", echo_nested_many) add_route(app, ["POST"], "/echo_nested_many_data_key", echo_nested_many_data_key) add_route(app, ["GET"], "/echo_match_info/{mymatch}", echo_match_info) add_route(app, ["GET"], "/echo_method", EchoHandler().get) add_route(app, ["GET"], "/echo_method_view", EchoHandlerView) add_route(app, ["GET"], "/echo_use_schema_as_kwargs", echo_use_schema_as_kwargs) return app python-webargs_8.0.1.orig/tests/apps/bottle_app.py0000644000000000000000000000763713775063555017305 0ustar00from bottle import Bottle, HTTPResponse, debug, request, response import marshmallow as ma from webargs import fields from webargs.bottleparser import parser, use_args, use_kwargs from webargs.core import json hello_args = {"name": fields.Str(missing="World", validate=lambda n: len(n) >= 3)} hello_multiple = {"name": fields.List(fields.Str())} class HelloSchema(ma.Schema): name = fields.Str(missing="World", validate=lambda n: len(n) >= 3) hello_many_schema = HelloSchema(many=True) # variant which ignores unknown fields hello_exclude_schema = HelloSchema(unknown=ma.EXCLUDE) app = Bottle() debug(True) @app.route("/echo", method=["GET"]) def echo(): return parser.parse(hello_args, request, location="query") @app.route("/echo_form", method=["POST"]) def echo_form(): return parser.parse(hello_args, location="form") @app.route("/echo_json", method=["POST"]) def echo_json(): return parser.parse(hello_args, location="json") @app.route("/echo_json_or_form", method=["POST"]) def echo_json_or_form(): return parser.parse(hello_args, location="json_or_form") @app.route("/echo_use_args", method=["GET"]) @use_args(hello_args, location="query") def echo_use_args(args): return args @app.route( "/echo_use_args_validated", method=["POST"], apply=use_args( {"value": fields.Int()}, validate=lambda args: args["value"] > 42, location="form", ), ) def echo_use_args_validated(args): return args @app.route("/echo_ignoring_extra_data", method=["POST"]) def echo_json_ignore_extra_data(): return parser.parse(hello_exclude_schema, unknown=None) @app.route( "/echo_use_kwargs", method=["GET"], apply=use_kwargs(hello_args, location="query") ) def echo_use_kwargs(name): return {"name": name} @app.route("/echo_multi", method=["GET"]) def echo_multi(): return parser.parse(hello_multiple, request, location="query") @app.route("/echo_multi_form", method=["POST"]) def multi_form(): return parser.parse(hello_multiple, location="form") @app.route("/echo_multi_json", method=["POST"]) def multi_json(): return parser.parse(hello_multiple) @app.route("/echo_many_schema", method=["POST"]) def echo_many_schema(): arguments = parser.parse(hello_many_schema, request) return HTTPResponse(body=json.dumps(arguments), content_type="application/json") @app.route( "/echo_use_args_with_path_param/", apply=use_args({"value": fields.Int()}, location="query"), ) def echo_use_args_with_path_param(args, name): return args @app.route( "/echo_use_kwargs_with_path_param/", apply=use_kwargs({"value": fields.Int()}, location="query"), ) def echo_use_kwargs_with_path_param(name, value): return {"value": value} @app.route("/error", method=["GET", "POST"]) def always_error(): def always_fail(value): raise ma.ValidationError("something went wrong") args = {"text": fields.Str(validate=always_fail)} return parser.parse(args) @app.route("/echo_headers") def echo_headers(): return parser.parse(hello_args, request, location="headers") @app.route("/echo_cookie") def echo_cookie(): return parser.parse(hello_args, request, location="cookies") @app.route("/echo_file", method=["POST"]) def echo_file(): args = {"myfile": fields.Field()} result = parser.parse(args, location="files") myfile = result["myfile"] content = myfile.file.read().decode("utf8") return {"myfile": content} @app.route("/echo_nested", method=["POST"]) def echo_nested(): args = {"name": fields.Nested({"first": fields.Str(), "last": fields.Str()})} return parser.parse(args) @app.route("/echo_nested_many", method=["POST"]) def echo_nested_many(): args = { "users": fields.Nested({"id": fields.Int(), "name": fields.Str()}, many=True) } return parser.parse(args) @app.error(400) @app.error(422) def handle_error(err): response.content_type = "application/json" return err.body python-webargs_8.0.1.orig/tests/apps/django_app/0000755000000000000000000000000013326636200016650 5ustar00python-webargs_8.0.1.orig/tests/apps/falcon_app.py0000644000000000000000000001301313775063555017237 0ustar00import falcon import marshmallow as ma from webargs import fields from webargs.core import json from webargs.falconparser import parser, use_args, use_kwargs hello_args = {"name": fields.Str(missing="World", validate=lambda n: len(n) >= 3)} hello_multiple = {"name": fields.List(fields.Str())} class HelloSchema(ma.Schema): name = fields.Str(missing="World", validate=lambda n: len(n) >= 3) hello_many_schema = HelloSchema(many=True) # variant which ignores unknown fields hello_exclude_schema = HelloSchema(unknown=ma.EXCLUDE) class Echo: def on_get(self, req, resp): parsed = parser.parse(hello_args, req, location="query") resp.body = json.dumps(parsed) class EchoForm: def on_post(self, req, resp): parsed = parser.parse(hello_args, req, location="form") resp.body = json.dumps(parsed) class EchoJSON: def on_post(self, req, resp): parsed = parser.parse(hello_args, req, location="json") resp.body = json.dumps(parsed) class EchoMedia: def on_post(self, req, resp): parsed = parser.parse(hello_args, req, location="media") resp.body = json.dumps(parsed) class EchoJSONOrForm: def on_post(self, req, resp): parsed = parser.parse(hello_args, req, location="json_or_form") resp.body = json.dumps(parsed) class EchoUseArgs: @use_args(hello_args, location="query") def on_get(self, req, resp, args): resp.body = json.dumps(args) class EchoUseKwargs: @use_kwargs(hello_args, location="query") def on_get(self, req, resp, name): resp.body = json.dumps({"name": name}) class EchoUseArgsValidated: @use_args( {"value": fields.Int()}, validate=lambda args: args["value"] > 42, location="form", ) def on_post(self, req, resp, args): resp.body = json.dumps(args) class EchoJSONIgnoreExtraData: def on_post(self, req, resp): resp.body = json.dumps(parser.parse(hello_exclude_schema, req, unknown=None)) class EchoMulti: def on_get(self, req, resp): resp.body = json.dumps(parser.parse(hello_multiple, req, location="query")) class EchoMultiForm: def on_post(self, req, resp): resp.body = json.dumps(parser.parse(hello_multiple, req, location="form")) class EchoMultiJSON: def on_post(self, req, resp): resp.body = json.dumps(parser.parse(hello_multiple, req)) class EchoManySchema: def on_post(self, req, resp): resp.body = json.dumps(parser.parse(hello_many_schema, req)) class EchoUseArgsWithPathParam: @use_args({"value": fields.Int()}, location="query") def on_get(self, req, resp, args, name): resp.body = json.dumps(args) class EchoUseKwargsWithPathParam: @use_kwargs({"value": fields.Int()}, location="query") def on_get(self, req, resp, value, name): resp.body = json.dumps({"value": value}) class AlwaysError: def on_get(self, req, resp): def always_fail(value): raise ma.ValidationError("something went wrong") args = {"text": fields.Str(validate=always_fail)} resp.body = json.dumps(parser.parse(args, req)) on_post = on_get class EchoHeaders: def on_get(self, req, resp): class HeaderSchema(ma.Schema): NAME = fields.Str(missing="World") resp.body = json.dumps(parser.parse(HeaderSchema(), req, location="headers")) class EchoCookie: def on_get(self, req, resp): resp.body = json.dumps(parser.parse(hello_args, req, location="cookies")) class EchoNested: def on_post(self, req, resp): args = {"name": fields.Nested({"first": fields.Str(), "last": fields.Str()})} resp.body = json.dumps(parser.parse(args, req)) class EchoNestedMany: def on_post(self, req, resp): args = { "users": fields.Nested( {"id": fields.Int(), "name": fields.Str()}, many=True ) } resp.body = json.dumps(parser.parse(args, req)) def use_args_hook(args, context_key="args", **kwargs): def hook(req, resp, resource, params): parsed_args = parser.parse(args, req=req, **kwargs) req.context[context_key] = parsed_args return hook @falcon.before(use_args_hook(hello_args, location="query")) class EchoUseArgsHook: def on_get(self, req, resp): resp.body = json.dumps(req.context["args"]) def create_app(): app = falcon.API() app.add_route("/echo", Echo()) app.add_route("/echo_form", EchoForm()) app.add_route("/echo_json", EchoJSON()) app.add_route("/echo_media", EchoMedia()) app.add_route("/echo_json_or_form", EchoJSONOrForm()) app.add_route("/echo_use_args", EchoUseArgs()) app.add_route("/echo_use_kwargs", EchoUseKwargs()) app.add_route("/echo_use_args_validated", EchoUseArgsValidated()) app.add_route("/echo_ignoring_extra_data", EchoJSONIgnoreExtraData()) app.add_route("/echo_multi", EchoMulti()) app.add_route("/echo_multi_form", EchoMultiForm()) app.add_route("/echo_multi_json", EchoMultiJSON()) app.add_route("/echo_many_schema", EchoManySchema()) app.add_route("/echo_use_args_with_path_param/{name}", EchoUseArgsWithPathParam()) app.add_route( "/echo_use_kwargs_with_path_param/{name}", EchoUseKwargsWithPathParam() ) app.add_route("/error", AlwaysError()) app.add_route("/echo_headers", EchoHeaders()) app.add_route("/echo_cookie", EchoCookie()) app.add_route("/echo_nested", EchoNested()) app.add_route("/echo_nested_many", EchoNestedMany()) app.add_route("/echo_use_args_hook", EchoUseArgsHook()) return app python-webargs_8.0.1.orig/tests/apps/flask_app.py0000644000000000000000000001300113775063555017072 0ustar00from flask import Flask, jsonify as J, Response, request from flask.views import MethodView import marshmallow as ma from webargs import fields from webargs.flaskparser import parser, use_args, use_kwargs from webargs.core import json class TestAppConfig: TESTING = True hello_args = {"name": fields.Str(missing="World", validate=lambda n: len(n) >= 3)} hello_multiple = {"name": fields.List(fields.Str())} class HelloSchema(ma.Schema): name = fields.Str(missing="World", validate=lambda n: len(n) >= 3) hello_many_schema = HelloSchema(many=True) app = Flask(__name__) app.config.from_object(TestAppConfig) @app.route("/echo", methods=["GET"]) def echo(): return J(parser.parse(hello_args, location="query")) @app.route("/echo_form", methods=["POST"]) def echo_form(): return J(parser.parse(hello_args, location="form")) @app.route("/echo_json", methods=["POST"]) def echo_json(): return J(parser.parse(hello_args, location="json")) @app.route("/echo_json_or_form", methods=["POST"]) def echo_json_or_form(): return J(parser.parse(hello_args, location="json_or_form")) @app.route("/echo_use_args", methods=["GET"]) @use_args(hello_args, location="query") def echo_use_args(args): return J(args) @app.route("/echo_use_args_validated", methods=["POST"]) @use_args( {"value": fields.Int()}, validate=lambda args: args["value"] > 42, location="form" ) def echo_use_args_validated(args): return J(args) @app.route("/echo_ignoring_extra_data", methods=["POST"]) def echo_json_ignore_extra_data(): return J(parser.parse(hello_args, unknown=ma.EXCLUDE)) @app.route("/echo_use_kwargs", methods=["GET"]) @use_kwargs(hello_args, location="query") def echo_use_kwargs(name): return J({"name": name}) @app.route("/echo_multi", methods=["GET"]) def multi(): return J(parser.parse(hello_multiple, location="query")) @app.route("/echo_multi_form", methods=["POST"]) def multi_form(): return J(parser.parse(hello_multiple, location="form")) @app.route("/echo_multi_json", methods=["POST"]) def multi_json(): return J(parser.parse(hello_multiple)) @app.route("/echo_many_schema", methods=["GET", "POST"]) def many_nested(): arguments = parser.parse(hello_many_schema) return Response(json.dumps(arguments), content_type="application/json") @app.route("/echo_use_args_with_path_param/") @use_args({"value": fields.Int()}, location="query") def echo_use_args_with_path(args, name): return J(args) @app.route("/echo_use_kwargs_with_path_param/") @use_kwargs({"value": fields.Int()}, location="query") def echo_use_kwargs_with_path(name, value): return J({"value": value}) @app.route("/error", methods=["GET", "POST"]) def error(): def always_fail(value): raise ma.ValidationError("something went wrong") args = {"text": fields.Str(validate=always_fail)} return J(parser.parse(args)) @app.route("/echo_headers") def echo_headers(): return J(parser.parse(hello_args, location="headers")) # as above, but in this case, turn off the default `EXCLUDE` behavior for # `headers`, so that errors will be raised @app.route("/echo_headers_raising") @use_args(HelloSchema(), location="headers", unknown=None) def echo_headers_raising(args): return J(args) @app.route("/echo_cookie") def echo_cookie(): return J(parser.parse(hello_args, request, location="cookies")) @app.route("/echo_file", methods=["POST"]) def echo_file(): args = {"myfile": fields.Field()} result = parser.parse(args, location="files") fp = result["myfile"] content = fp.read().decode("utf8") return J({"myfile": content}) @app.route("/echo_view_arg/") def echo_view_arg(view_arg): return J(parser.parse({"view_arg": fields.Int()}, location="view_args")) @app.route("/echo_view_arg_use_args/") @use_args({"view_arg": fields.Int()}, location="view_args") def echo_view_arg_with_use_args(args, **kwargs): return J(args) @app.route("/echo_nested", methods=["POST"]) def echo_nested(): args = {"name": fields.Nested({"first": fields.Str(), "last": fields.Str()})} return J(parser.parse(args)) @app.route("/echo_nested_many", methods=["POST"]) def echo_nested_many(): args = { "users": fields.Nested({"id": fields.Int(), "name": fields.Str()}, many=True) } return J(parser.parse(args)) @app.route("/echo_nested_many_data_key", methods=["POST"]) def echo_nested_many_with_data_key(): args = { "x_field": fields.Nested({"id": fields.Int()}, many=True, data_key="X-Field") } return J(parser.parse(args)) class EchoMethodViewUseArgs(MethodView): @use_args({"val": fields.Int()}) def post(self, args): return J(args) app.add_url_rule( "/echo_method_view_use_args", view_func=EchoMethodViewUseArgs.as_view("echo_method_view_use_args"), ) class EchoMethodViewUseKwargs(MethodView): @use_kwargs({"val": fields.Int()}) def post(self, val): return J({"val": val}) app.add_url_rule( "/echo_method_view_use_kwargs", view_func=EchoMethodViewUseKwargs.as_view("echo_method_view_use_kwargs"), ) @app.route("/echo_use_kwargs_missing", methods=["post"]) @use_kwargs({"username": fields.Str(required=True), "password": fields.Str()}) def echo_use_kwargs_missing(username, **kwargs): assert "password" not in kwargs return J({"username": username}) # Return validation errors as JSON @app.errorhandler(422) @app.errorhandler(400) def handle_error(err): if err.code == 422: assert isinstance(err.data["schema"], ma.Schema) return J(err.data["messages"]), err.code python-webargs_8.0.1.orig/tests/apps/pyramid_app.py0000644000000000000000000001332113775063555017444 0ustar00from pyramid.config import Configurator from pyramid.httpexceptions import HTTPBadRequest import marshmallow as ma from webargs import fields from webargs.pyramidparser import parser, use_args, use_kwargs from webargs.core import json hello_args = {"name": fields.Str(missing="World", validate=lambda n: len(n) >= 3)} hello_multiple = {"name": fields.List(fields.Str())} class HelloSchema(ma.Schema): name = fields.Str(missing="World", validate=lambda n: len(n) >= 3) hello_many_schema = HelloSchema(many=True) # variant which ignores unknown fields hello_exclude_schema = HelloSchema(unknown=ma.EXCLUDE) def echo(request): return parser.parse(hello_args, request, location="query") def echo_form(request): return parser.parse(hello_args, request, location="form") def echo_json(request): try: return parser.parse(hello_args, request, location="json") except json.JSONDecodeError: error = HTTPBadRequest() error.body = json.dumps(["Invalid JSON."]).encode("utf-8") error.content_type = "application/json" raise error def echo_json_or_form(request): try: return parser.parse(hello_args, request, location="json_or_form") except json.JSONDecodeError: error = HTTPBadRequest() error.body = json.dumps(["Invalid JSON."]).encode("utf-8") error.content_type = "application/json" raise error def echo_json_ignore_extra_data(request): try: return parser.parse(hello_exclude_schema, request, unknown=None) except json.JSONDecodeError: error = HTTPBadRequest() error.body = json.dumps(["Invalid JSON."]).encode("utf-8") error.content_type = "application/json" raise error def echo_query(request): return parser.parse(hello_args, request, location="query") @use_args(hello_args, location="query") def echo_use_args(request, args): return args @use_args( {"value": fields.Int()}, validate=lambda args: args["value"] > 42, location="form" ) def echo_use_args_validated(request, args): return args @use_kwargs(hello_args, location="query") def echo_use_kwargs(request, name): return {"name": name} def echo_multi(request): return parser.parse(hello_multiple, request, location="query") def echo_multi_form(request): return parser.parse(hello_multiple, request, location="form") def echo_multi_json(request): return parser.parse(hello_multiple, request) def echo_many_schema(request): return parser.parse(hello_many_schema, request) @use_args({"value": fields.Int()}, location="query") def echo_use_args_with_path_param(request, args): return args @use_kwargs({"value": fields.Int()}, location="query") def echo_use_kwargs_with_path_param(request, value): return {"value": value} def always_error(request): def always_fail(value): raise ma.ValidationError("something went wrong") argmap = {"text": fields.Str(validate=always_fail)} return parser.parse(argmap, request) def echo_headers(request): return parser.parse(hello_args, request, location="headers") def echo_cookie(request): return parser.parse(hello_args, request, location="cookies") def echo_file(request): args = {"myfile": fields.Field()} result = parser.parse(args, request, location="files") myfile = result["myfile"] content = myfile.file.read().decode("utf8") return {"myfile": content} def echo_nested(request): argmap = {"name": fields.Nested({"first": fields.Str(), "last": fields.Str()})} return parser.parse(argmap, request) def echo_nested_many(request): argmap = { "users": fields.Nested({"id": fields.Int(), "name": fields.Str()}, many=True) } return parser.parse(argmap, request) def echo_matchdict(request): return parser.parse({"mymatch": fields.Int()}, request, location="matchdict") class EchoCallable: def __init__(self, request): self.request = request @use_args({"value": fields.Int()}, location="query") def __call__(self, args): return args def add_route(config, route, view, route_name=None, renderer="json"): """Helper for adding a new route-view pair.""" route_name = route_name or view.__name__ config.add_route(route_name, route) config.add_view(view, route_name=route_name, renderer=renderer) def create_app(): config = Configurator() add_route(config, "/echo", echo) add_route(config, "/echo_form", echo_form) add_route(config, "/echo_json", echo_json) add_route(config, "/echo_json_or_form", echo_json_or_form) add_route(config, "/echo_query", echo_query) add_route(config, "/echo_ignoring_extra_data", echo_json_ignore_extra_data) add_route(config, "/echo_use_args", echo_use_args) add_route(config, "/echo_use_args_validated", echo_use_args_validated) add_route(config, "/echo_use_kwargs", echo_use_kwargs) add_route(config, "/echo_multi", echo_multi) add_route(config, "/echo_multi_form", echo_multi_form) add_route(config, "/echo_multi_json", echo_multi_json) add_route(config, "/echo_many_schema", echo_many_schema) add_route( config, "/echo_use_args_with_path_param/{name}", echo_use_args_with_path_param ) add_route( config, "/echo_use_kwargs_with_path_param/{name}", echo_use_kwargs_with_path_param, ) add_route(config, "/error", always_error) add_route(config, "/echo_headers", echo_headers) add_route(config, "/echo_cookie", echo_cookie) add_route(config, "/echo_file", echo_file) add_route(config, "/echo_nested", echo_nested) add_route(config, "/echo_nested_many", echo_nested_many) add_route(config, "/echo_callable", EchoCallable) add_route(config, "/echo_matchdict/{mymatch}", echo_matchdict) return config.make_wsgi_app() python-webargs_8.0.1.orig/tests/apps/django_app/__init__.py0000644000000000000000000000000013326636200020747 0ustar00python-webargs_8.0.1.orig/tests/apps/django_app/base/0000755000000000000000000000000013326636200017562 5ustar00python-webargs_8.0.1.orig/tests/apps/django_app/echo/0000755000000000000000000000000013326636200017566 5ustar00python-webargs_8.0.1.orig/tests/apps/django_app/manage.py0000755000000000000000000000037213326636200020457 0ustar00#!/usr/bin/env python import os import sys if __name__ == "__main__": os.environ.setdefault("DJANGO_SETTINGS_MODULE", "testapp.settings") from django.core.management import execute_from_command_line execute_from_command_line(sys.argv) python-webargs_8.0.1.orig/tests/apps/django_app/base/__init__.py0000644000000000000000000000000013326636200021661 0ustar00python-webargs_8.0.1.orig/tests/apps/django_app/base/settings.py0000644000000000000000000000147313775063555022020 0ustar00# Build paths inside the project like this: os.path.join(BASE_DIR, ...) import os BASE_DIR = os.path.dirname(os.path.dirname(__file__)) SECRET_KEY = "s$28!(eonml-m3jgbq_)bj_&#=)sym2d*kx%@j+r&vwusxz%g$" DEBUG = True TEMPLATE_DEBUG = True ALLOWED_HOSTS = ["*"] # Application definition INSTALLED_APPS = ("django.contrib.contenttypes",) MIDDLEWARE_CLASSES = ( "django.contrib.sessions.middleware.SessionMiddleware", "django.middleware.common.CommonMiddleware", "django.contrib.messages.middleware.MessageMiddleware", "django.middleware.clickjacking.XFrameOptionsMiddleware", ) ROOT_URLCONF = "tests.apps.django_app.base.urls" WSGI_APPLICATION = "tests.apps.django_app.base.wsgi.application" LANGUAGE_CODE = "en-us" TIME_ZONE = "UTC" USE_I18N = True USE_L10N = True USE_TZ = True STATIC_URL = "/static/" python-webargs_8.0.1.orig/tests/apps/django_app/base/urls.py0000644000000000000000000000272013775063555021141 0ustar00from django.conf.urls import url from tests.apps.django_app.echo import views urlpatterns = [ url(r"^echo$", views.echo), url(r"^echo_form$", views.echo_form), url(r"^echo_json$", views.echo_json), url(r"^echo_json_or_form$", views.echo_json_or_form), url(r"^echo_use_args$", views.echo_use_args), url(r"^echo_use_args_validated$", views.echo_use_args_validated), url(r"^echo_ignoring_extra_data$", views.echo_ignoring_extra_data), url(r"^echo_use_kwargs$", views.echo_use_kwargs), url(r"^echo_multi$", views.echo_multi), url(r"^echo_multi_form$", views.echo_multi_form), url(r"^echo_multi_json$", views.echo_multi_json), url(r"^echo_many_schema$", views.echo_many_schema), url( r"^echo_use_args_with_path_param/(?P\w+)$", views.echo_use_args_with_path_param, ), url( r"^echo_use_kwargs_with_path_param/(?P\w+)$", views.echo_use_kwargs_with_path_param, ), url(r"^error$", views.always_error), url(r"^echo_headers$", views.echo_headers), url(r"^echo_cookie$", views.echo_cookie), url(r"^echo_file$", views.echo_file), url(r"^echo_nested$", views.echo_nested), url(r"^echo_nested_many$", views.echo_nested_many), url(r"^echo_cbv$", views.EchoCBV.as_view()), url(r"^echo_use_args_cbv$", views.EchoUseArgsCBV.as_view()), url( r"^echo_use_args_with_path_param_cbv/(?P\d+)$", views.EchoUseArgsWithParamCBV.as_view(), ), ] python-webargs_8.0.1.orig/tests/apps/django_app/base/wsgi.py0000644000000000000000000000064313326636200021110 0ustar00""" WSGI config for helloapp project. It exposes the WSGI callable as a module-level variable named ``application``. For more information on this file, see https://docs.djangoproject.com/en/1.6/howto/deployment/wsgi/ """ import os os.environ.setdefault("DJANGO_SETTINGS_MODULE", "tests.apps.django_app.base.settings") from django.core.wsgi import get_wsgi_application # noqa application = get_wsgi_application() python-webargs_8.0.1.orig/tests/apps/django_app/echo/__init__.py0000644000000000000000000000000013326636200021665 0ustar00python-webargs_8.0.1.orig/tests/apps/django_app/echo/views.py0000644000000000000000000001164513775063555021323 0ustar00from django.http import HttpResponse from django.views.generic import View import marshmallow as ma from webargs import fields from webargs.djangoparser import parser, use_args, use_kwargs from webargs.core import json hello_args = {"name": fields.Str(missing="World", validate=lambda n: len(n) >= 3)} hello_multiple = {"name": fields.List(fields.Str())} class HelloSchema(ma.Schema): name = fields.Str(missing="World", validate=lambda n: len(n) >= 3) hello_many_schema = HelloSchema(many=True) # variant which ignores unknown fields hello_exclude_schema = HelloSchema(unknown=ma.EXCLUDE) def json_response(data, **kwargs): return HttpResponse(json.dumps(data), content_type="application/json", **kwargs) def handle_view_errors(f): def wrapped(*args, **kwargs): try: return f(*args, **kwargs) except ma.ValidationError as err: return json_response(err.messages, status=422) except json.JSONDecodeError: return json_response({"json": ["Invalid JSON body."]}, status=400) return wrapped @handle_view_errors def echo(request): return json_response(parser.parse(hello_args, request, location="query")) @handle_view_errors def echo_form(request): return json_response(parser.parse(hello_args, request, location="form")) @handle_view_errors def echo_json(request): return json_response(parser.parse(hello_args, request, location="json")) @handle_view_errors def echo_json_or_form(request): return json_response(parser.parse(hello_args, request, location="json_or_form")) @handle_view_errors @use_args(hello_args, location="query") def echo_use_args(request, args): return json_response(args) @handle_view_errors @use_args( {"value": fields.Int()}, validate=lambda args: args["value"] > 42, location="form" ) def echo_use_args_validated(args): return json_response(args) @handle_view_errors def echo_ignoring_extra_data(request): return json_response(parser.parse(hello_exclude_schema, request, unknown=None)) @handle_view_errors @use_kwargs(hello_args, location="query") def echo_use_kwargs(request, name): return json_response({"name": name}) @handle_view_errors def echo_multi(request): return json_response(parser.parse(hello_multiple, request, location="query")) @handle_view_errors def echo_multi_form(request): return json_response(parser.parse(hello_multiple, request, location="form")) @handle_view_errors def echo_multi_json(request): return json_response(parser.parse(hello_multiple, request)) @handle_view_errors def echo_many_schema(request): return json_response(parser.parse(hello_many_schema, request)) @handle_view_errors @use_args({"value": fields.Int()}, location="query") def echo_use_args_with_path_param(request, args, name): return json_response(args) @handle_view_errors @use_kwargs({"value": fields.Int()}, location="query") def echo_use_kwargs_with_path_param(request, value, name): return json_response({"value": value}) @handle_view_errors def always_error(request): def always_fail(value): raise ma.ValidationError("something went wrong") argmap = {"text": fields.Str(validate=always_fail)} return parser.parse(argmap, request) @handle_view_errors def echo_headers(request): return json_response(parser.parse(hello_args, request, location="headers")) @handle_view_errors def echo_cookie(request): return json_response(parser.parse(hello_args, request, location="cookies")) @handle_view_errors def echo_file(request): args = {"myfile": fields.Field()} result = parser.parse(args, request, location="files") myfile = result["myfile"] content = myfile.read().decode("utf8") return json_response({"myfile": content}) @handle_view_errors def echo_nested(request): argmap = {"name": fields.Nested({"first": fields.Str(), "last": fields.Str()})} return json_response(parser.parse(argmap, request)) @handle_view_errors def echo_nested_many(request): argmap = { "users": fields.Nested({"id": fields.Int(), "name": fields.Str()}, many=True) } return json_response(parser.parse(argmap, request)) class EchoCBV(View): @handle_view_errors def get(self, request): location_kwarg = {} if request.method == "POST" else {"location": "query"} return json_response(parser.parse(hello_args, self.request, **location_kwarg)) post = get class EchoUseArgsCBV(View): @handle_view_errors @use_args(hello_args, location="query") def get(self, request, args): return json_response(args) @handle_view_errors @use_args(hello_args) def post(self, request, args): return json_response(args) class EchoUseArgsWithParamCBV(View): @handle_view_errors @use_args(hello_args, location="query") def get(self, request, args, pid): return json_response(args) @handle_view_errors @use_args(hello_args) def post(self, request, args, pid): return json_response(args)