pax_global_header00006660000000000000000000000064145465450230014522gustar00rootroot0000000000000052 comment=069f4f927d1440c77da241fa517da059bf06f86e python-djangorestframework-yaml-3.0.1/000077500000000000000000000000001454654502300200605ustar00rootroot00000000000000python-djangorestframework-yaml-3.0.1/.github/000077500000000000000000000000001454654502300214205ustar00rootroot00000000000000python-djangorestframework-yaml-3.0.1/.github/workflows/000077500000000000000000000000001454654502300234555ustar00rootroot00000000000000python-djangorestframework-yaml-3.0.1/.github/workflows/ci.yml000066400000000000000000000060051454654502300245740ustar00rootroot00000000000000name: CI env: PYTHON_VERSION: '3.11' POETRY_VERSION: '1.4.2' on: push: branches: [main] pull_request: branches: [main] jobs: tests: name: "Python ${{ matrix.python-version }} on ${{ matrix.platform }}" runs-on: "${{ matrix.platform }}" strategy: matrix: platform: [ubuntu-latest] python-version: ["3.8", "3.9", "3.10", "3.11"] steps: - name: Check out repository uses: actions/checkout@v3 - name: Set up python uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - name: Install dependencies run: | python -VV python -m pip install --upgrade virtualenv tox tox-gh-actions - name: "Run tox targets for ${{ matrix.python-version }}" run: python -m tox env: PLATFORM: ${{ matrix.platform }} coverage: name: "Test coverage" runs-on: ubuntu-latest steps: - name: Check out repository uses: actions/checkout@v3 - name: Set up python uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - name: Install dependencies run: | python -VV python -m pip install --upgrade virtualenv tox - name: Run tests to collect coverage run: python -m tox -e coverage - name: Upload coverage uses: coverallsapp/github-action@v2 with: path-to-lcov: ./coverage.lcov env: COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }} package: name: Build & verify package runs-on: ubuntu-latest steps: - name: Check out repository uses: actions/checkout@v3 - name: Set up python uses: actions/setup-python@v4 with: python-version: ${{ env.PYTHON_VERSION }} - name: Load cached Poetry installation id: cached-poetry uses: actions/cache@v3 with: path: ~/.local # the path depends on the OS key: poetry-0 # increment to reset cache - name: Install and configure Poetry if: steps.cached-poetry.outputs.cache-hit != 'true' uses: snok/install-poetry@v1 with: virtualenvs-create: true virtualenvs-in-project: true - name: Install dependencies run: poetry install --only main - name: Build package run: poetry build - name: Check long_description run: poetry run pip install twine && poetry run python -m twine check dist/* install-dev: strategy: matrix: os: [ubuntu-latest, windows-latest, macos-latest] name: Verify dev env runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 with: python-version: ${{env.PYTHON_VERSION}} - name: Install in dev mode run: python -m pip install -e .[dev] - name: Import package run: python -c 'import drf_yaml; print(drf_yaml.__version__)' python-djangorestframework-yaml-3.0.1/.github/workflows/mkdocs.yml000066400000000000000000000024311454654502300254600ustar00rootroot00000000000000name: Make docs env: PYTHON_VERSION: '3.11' POETRY_VERSION: '1.4.2' on: push: branches: [main] permissions: contents: write jobs: docs: name: Build docs & publish them runs-on: ubuntu-latest steps: - name: Check out repository default branch uses: actions/checkout@v3 - name: Check out repository default branch run: git fetch origin gh-pages - name: Set up python uses: actions/setup-python@v4 with: python-version: ${{ env.PYTHON_VERSION }} - name: Load cached Poetry installation id: cached-poetry uses: actions/cache@v3 with: path: ~/.local # the path depends on the OS key: poetry-0 # increment to reset cache - name: Install and configure Poetry if: steps.cached-poetry.outputs.cache-hit != 'true' uses: snok/install-poetry@v1 with: virtualenvs-create: true virtualenvs-in-project: true - name: Install dependencies run: poetry install --only docs - name: Build run: | git config --global user.name "Github Action" git config --global user.email "dontemailthis@users.noreply.github.com" poetry run mike deploy --update-aliases develop --push python-djangorestframework-yaml-3.0.1/.github/workflows/publish.yml000066400000000000000000000021521454654502300256460ustar00rootroot00000000000000name: Publish on: release: types: [created] env: PYTHON_VERSION: '3.11' POETRY_VERSION: '1.4.2' jobs: deploy: runs-on: ubuntu-latest steps: - name: Check out repository uses: actions/checkout@v3 - name: Set up python uses: actions/setup-python@v4 with: python-version: ${{ env.PYTHON_VERSION }} - name: Load cached Poetry installation id: cached-poetry uses: actions/cache@v3 with: path: ~/.local # the path depends on the OS key: poetry-0 # increment to reset cache - name: Install and configure Poetry if: steps.cached-poetry.outputs.cache-hit != 'true' uses: snok/install-poetry@v1 with: virtualenvs-create: true virtualenvs-in-project: true - name: Validate poetry config run: poetry check - name: Install dependencies run: poetry install - name: Build run: poetry build - name: Publish run: poetry publish env: POETRY_PYPI_TOKEN_PYPI: ${{ secrets.POETRY_PYPI_TOKEN_PYPI }} python-djangorestframework-yaml-3.0.1/.gitignore000066400000000000000000000003331454654502300220470ustar00rootroot00000000000000*.pyc *.db *~ .* html/ htmlcov/ coverage/ build/ dist/ *.egg-info/ MANIFEST bin/ include/ lib/ local/ !.gitignore !.github !.flake8 !.isort.cfg !.pre-commit-config.yaml pip-wheel-metadata poetry.lock coverage.lcov python-djangorestframework-yaml-3.0.1/.pre-commit-config.yaml000066400000000000000000000010361454654502300243410ustar00rootroot00000000000000repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.4.0 hooks: - id: trailing-whitespace - id: debug-statements - id: check-json - id: check-yaml - id: check-toml - id: check-merge-conflict - id: forbid-submodules - id: mixed-line-ending - repo: https://github.com/psf/black rev: 23.3.0 hooks: - id: black language_version: python3.11 - repo: https://github.com/charliermarsh/ruff-pre-commit rev: v0.0.263 hooks: - id: ruff python-djangorestframework-yaml-3.0.1/CHANGELOG.md000066400000000000000000000025251454654502300216750ustar00rootroot00000000000000# Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] ## [3.0.1] ### Fixed - All DRF fields. ### Removed - `.flake8` file - `requirements` file ## [3.0.0] - 2023-06-02 ### Added - Test / Support for `Python>=3.8` - Test / Support for `Django>=3.2` - Test / Support for `DRF>=3.2` - Support for `DRF==3.14` - Test coverage. - Integration with `mkdocs-material` with `mike` support for versioned docs. - Typing annotations. - Added string styles. - Added representers for UUID, Time, TimeDelta, ErrorDetail and SafeString. ### Changed - Replace `setuptools` with `poetry`. - Replace `isort` and `flake8` by ruff. ### Removed - Support for `Python<3.8` - Support for `Django<3.2` - Support for `DRF==3.14` ## [2.0.0] - 2020-04-27 ### Changed - Update test/support matrix: `Python 3.5+`, `Django 2.2+`, `DRF 3.11+`. ### Removed - Support for Python 2. [unreleased]: https://github.com/Qu4tro/drf-yaml/compare/3.0.1...HEAD [3.0.1]: https://github.com/Qu4tro/drf-yaml/compare/3.0.0...3.0.1 [3.0.0]: https://github.com/Qu4tro/drf-yaml/releases/tag/3.0.0 [2.0.0]: https://github.com/jpadilla/django-rest-framework-yaml/releases/tag/2.0.0 python-djangorestframework-yaml-3.0.1/LICENSE000066400000000000000000000027601454654502300210720ustar00rootroot00000000000000Copyright (c) 2014, José Padilla All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder 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. python-djangorestframework-yaml-3.0.1/README.md000066400000000000000000000036731454654502300213500ustar00rootroot00000000000000# REST Framework YAML ![build-status-image] [![pypi-version]][pypi] **YAML support for Django REST Framework** Full documentation for the project is available at [http://qu4tro.github.io/drf-yaml][docs]. ## Overview YAML support for the Django REST Framework, forked from [https://github.com/jpadilla/django-rest-framework-yaml][original]. ## Requirements * Python (3.8, 3.9, 3.10, 3.11) * Django (3.2, 4.*) ## Installation Install using `pip`... ```bash $ pip install drf-yaml ``` ## Example ```python REST_FRAMEWORK = { 'DEFAULT_PARSER_CLASSES': ( 'drf_yaml.parsers.YAMLParser', ), 'DEFAULT_RENDERER_CLASSES': ( 'drf_yaml.renderers.YAMLRenderer', ), } ``` You can also set the renderer and parser used for an individual view, or viewset, using the APIView class based views. ```python from rest_framework import routers, serializers, viewsets from drf_yaml.parsers import YAMLParser from drf_yaml.renderers import YAMLRenderer # Serializers define the API representation. class UserSerializer(serializers.HyperlinkedModelSerializer): class Meta: model = User fields = ('url', 'username', 'email', 'is_staff') # ViewSets define the view behavior. class UserViewSet(viewsets.ModelViewSet): queryset = User.objects.all() serializer_class = UserSerializer parser_classes = (YAMLParser,) renderer_classes = (YAMLRenderer,) ``` ### Sample output ```yaml --- - email: jpadilla@example.com is_staff: true url: "http://127.0.0.1:8000/users/1/" username: jpadilla ``` ## Documentation & Support Full documentation for the project is available at [http://qu4tro.github.io/drf-yaml][docs]. [build-status-image]: https://img.shields.io/github/checks-status/Qu4tro/drf-yaml/main [pypi-version]: https://img.shields.io/pypi/v/drf-yaml.svg [pypi]: https://pypi.python.org/pypi/drf-yaml [docs]: http://qu4tro.github.io/drf-yaml [original]: https://github.com/jpadilla/django-rest-framework-yaml python-djangorestframework-yaml-3.0.1/RELEASE.md000066400000000000000000000006441454654502300214660ustar00rootroot00000000000000# How to create a new release - Create a new branch. - Update version on `drf_yaml/__init__.py`. - Update version on `pyproject.toml`. - Update CHANGELOG.md - Add new version below unreleased - Move unreleased to new version - Add compare link on the bottom - Commit - Push - Fix any CI issues - Run `poetry run mike deploy --update-aliases latest --push` - Merge - Create github release - Profit python-djangorestframework-yaml-3.0.1/docs/000077500000000000000000000000001454654502300210105ustar00rootroot00000000000000python-djangorestframework-yaml-3.0.1/docs/css/000077500000000000000000000000001454654502300216005ustar00rootroot00000000000000python-djangorestframework-yaml-3.0.1/docs/css/extra.css000066400000000000000000000005701454654502300234370ustar00rootroot00000000000000body.homepage div.col-md-9 h1:first-of-type { text-align: center; font-size: 60px; font-weight: 300; margin-top: 0; } body.homepage div.col-md-9 p:first-of-type { text-align: center; } body.homepage .badges { text-align: right; } body.homepage .badges a { display: inline-block; } body.homepage .badges a img { padding: 0; margin: 0; } python-djangorestframework-yaml-3.0.1/docs/index.md000066400000000000000000000042651454654502300224500ustar00rootroot00000000000000 --- # REST Framework YAML YAML support for Django REST Framework --- ## Overview YAML support for the Django REST Framework, forked from [https://github.com/jpadilla/django-rest-framework-yaml][original]. ## Requirements * Python (3.8, 3.9, 3.10, 3.11) * Django (3.2, 4.*) ## Installation Install using `pip`... ```bash $ pip install drf-yaml ``` ## Example ```python REST_FRAMEWORK = { 'DEFAULT_PARSER_CLASSES': ( 'drf_yaml.parsers.YAMLParser', ), 'DEFAULT_RENDERER_CLASSES': ( 'drf_yaml.renderers.YAMLRenderer', ), } ``` You can also set the renderer and parser used for an individual view, or viewset, using the APIView class based views. ```python from rest_framework.response import Response from rest_framework.views import APIView from drf_yaml.parsers import YAMLParser from drf_yaml.renderers import YAMLRenderer class ExampleView(APIView): """ A view that can accept POST requests with YAML content. """ parser_classes = (YAMLParser,) renderer_classes = (YAMLRenderer,) def post(self, request, format=None): return Response({'received data': request.DATA}) ``` ### Sample output ```yaml --- - email: jpadilla@example.com is_staff: true url: "http://127.0.0.1:8000/users/1/" username: jpadilla ``` ## Testing Install testing requirements. ```bash $ poetry install ``` Run with pytest. ```bash $ poetry run pytest ``` You can also use the excellent [tox](http://tox.readthedocs.org/en/latest/) testing tool to run the tests against all supported versions of Python and Django. Install tox globally, and then simply run: ```bash $ tox ``` ## Documentation To build the documentation, you'll need to install `mkdocs`. ```bash $ poetry install --only docs ``` To preview the documentation: ```bash $ poetry run mkdocs serve Running at: http://127.0.0.1:8000/ ``` To build the documentation: ``` $ poetry run mkdocs build ``` [original]: https://github.com/jpadilla/django-rest-framework-yaml python-djangorestframework-yaml-3.0.1/docs/overrides/000077500000000000000000000000001454654502300230125ustar00rootroot00000000000000python-djangorestframework-yaml-3.0.1/docs/overrides/main.html000066400000000000000000000003061454654502300246230ustar00rootroot00000000000000{% extends "base.html" %} {% block outdated %} You're not viewing the latest version. Click here to go to latest. {% endblock %} python-djangorestframework-yaml-3.0.1/docs/parsers.md000066400000000000000000000024471454654502300230200ustar00rootroot00000000000000# Parsers ## Setting the parsers The default set of parsers may be set globally, using the `DEFAULT_PARSER_CLASSES` setting. For example, the following settings would allow requests with `YAML` content. REST_FRAMEWORK = { 'DEFAULT_PARSER_CLASSES': ( 'drf_yaml.parsers.YAMLParser', ) } You can also set the parsers used for an individual view, or viewset, using the `APIView` class based views. from rest_framework.response import Response from rest_framework.views import APIView from drf_yaml.parsers import YAMLParser class ExampleView(APIView): """ A view that can accept POST requests with YAML content. """ parser_classes = (YAMLParser,) def post(self, request, format=None): return Response({'received data': request.DATA}) Or, if you're using the `@api_view` decorator with function based views. @api_view(['POST']) @parser_classes((YAMLParser,)) def example_view(request, format=None): """ A view that can accept POST requests with YAML content. """ return Response({'received data': request.DATA}) --- # API Reference ## YAMLParser Parses `YAML` request content. Requires the `pyyaml` package to be installed. **.media_type**: `application/yaml` python-djangorestframework-yaml-3.0.1/docs/renderers.md000066400000000000000000000034041454654502300233240ustar00rootroot00000000000000# Renderers ## Setting the renderers The default set of renderers may be set globally, using the `DEFAULT_RENDERER_CLASSES` setting. For example, the following settings would use `YAML` as the main media type and also include the self describing API. REST_FRAMEWORK = { 'DEFAULT_RENDERER_CLASSES': ( 'drf_yaml.renderers.YAMLRenderer', ) } You can also set the renderers used for an individual view, or viewset, using the `APIView` class based views. from django.contrib.auth.models import User from rest_framework.response import Response from rest_framework.views import APIView from drf_yaml.renderers import YAMLRenderer class UserCountView(APIView): """ A view that returns the count of active users in YAML. """ renderer_classes = (YAMLRenderer,) def get(self, request, format=None): user_count = User.objects.filter(active=True).count() content = {'user_count': user_count} return Response(content) Or, if you're using the `@api_view` decorator with function based views. @api_view(['GET']) @renderer_classes((YAMLRenderer,)) def user_count_view(request, format=None): """ A view that returns the count of active users in YAML. """ user_count = User.objects.filter(active=True).count() content = {'user_count': user_count} return Response(content) --- # API Reference ## YAMLRenderer Renders the request data into `YAML`. Requires the `pyyaml` package to be installed. Note that non-ascii characters will be rendered using `\uXXXX` character escape. For example: unicode black star: "\u2605" **.media_type**: `application/yaml` **.format**: `'.yaml'` **.charset**: `utf-8` python-djangorestframework-yaml-3.0.1/drf_yaml/000077500000000000000000000000001454654502300216555ustar00rootroot00000000000000python-djangorestframework-yaml-3.0.1/drf_yaml/__init__.py000066400000000000000000000001051454654502300237620ustar00rootroot00000000000000"""YAML support for Django REST Framework.""" __version__ = "3.0.1" python-djangorestframework-yaml-3.0.1/drf_yaml/encoders.py000066400000000000000000000101311454654502300240250ustar00rootroot00000000000000"""Helper classes for parsers.""" import decimal import types from collections import OrderedDict from datetime import time, timedelta from uuid import UUID import yaml from django.utils.encoding import force_str from django.utils.safestring import SafeString from rest_framework.exceptions import ErrorDetail from rest_framework.relations import Hyperlink from rest_framework.utils.serializer_helpers import ReturnDict, ReturnList from . import styles class SafeDumper(yaml.SafeDumper): """ Dumper subclass that handles commonly used DRF types. These are the supported types: - Decimal is represented as a string. - UUID is represented as a string. - Time is represented as a string. - TimeDelta is represented as a string. - Hyperlink is represented as a string. - ErrorDetail is represented as a string. - SafeString is represented as a string. - OrderedDict is represented as a dict. - ReturnDict is represented as a dict. - ReturnList is represented as a list. - Generators are represented as a list. It also supports the following custom types: - FoldedStr is represented as a string with the folded style. - LiteralStr is represented as a string with the literal style. - SingleQuotedStr is represented as a string with the single quoted style. - DoubleQuotedStr is represented as a string with the double quoted style. - FlowStyleSequence is represented as a list with the flow style. - FlowStyleMapping is represented as a dict with the flow style. These can be imported from drf_yaml.styles. """ SafeDumper.add_representer( decimal.Decimal, lambda dumper, data: dumper.represent_scalar( "tag:yaml.org,2002:str", force_str(data), ), ) SafeDumper.add_representer( UUID, lambda dumper, data: dumper.represent_scalar( "tag:yaml.org,2002:str", force_str(data), ), ) SafeDumper.add_representer( time, lambda dumper, data: dumper.represent_scalar( "tag:yaml.org,2002:str", force_str(data), ), ) SafeDumper.add_representer( timedelta, lambda dumper, data: dumper.represent_float( data.total_seconds(), ), ) SafeDumper.add_representer( bytes, lambda dumper, data: dumper.represent_scalar( "tag:yaml.org,2002:str", data.decode("utf-8"), ), ) SafeDumper.add_representer( OrderedDict, yaml.representer.SafeRepresenter.represent_dict, ) SafeDumper.add_representer( SafeString, yaml.representer.SafeRepresenter.represent_str, ) SafeDumper.add_representer( Hyperlink, yaml.representer.SafeRepresenter.represent_str, ) SafeDumper.add_representer( ErrorDetail, yaml.representer.SafeRepresenter.represent_str, ) SafeDumper.add_representer( ReturnDict, yaml.representer.SafeRepresenter.represent_dict, ) SafeDumper.add_representer( ReturnList, yaml.representer.SafeRepresenter.represent_list, ) SafeDumper.add_representer( types.GeneratorType, yaml.representer.SafeRepresenter.represent_list, ) SafeDumper.add_representer( styles.FoldedStr, lambda dumper, data: dumper.represent_scalar( "tag:yaml.org,2002:str", data, style=">", ), ) SafeDumper.add_representer( styles.LiteralStr, lambda dumper, data: dumper.represent_scalar( "tag:yaml.org,2002:str", data, style="|", ), ) SafeDumper.add_representer( styles.SingleQuotedStr, lambda dumper, data: dumper.represent_scalar( "tag:yaml.org,2002:str", data, style="'", ), ) SafeDumper.add_representer( styles.DoubleQuotedStr, lambda dumper, data: dumper.represent_scalar( "tag:yaml.org,2002:str", data, style='"', ), ) SafeDumper.add_representer( styles.FlowStyleSequence, lambda dumper, data: dumper.represent_sequence( "tag:yaml.org,2002:seq", data, flow_style=True, ), ) SafeDumper.add_representer( styles.FlowStyleMapping, lambda dumper, data: dumper.represent_mapping( "tag:yaml.org,2002:map", data, flow_style=True, ), ) python-djangorestframework-yaml-3.0.1/drf_yaml/fields.py000066400000000000000000000035641454654502300235050ustar00rootroot00000000000000""" Custom DRF fields for use in serialization when using the YAMLRenderer. These fields are used to render the YAML in a more readable way. """ from typing import Any from rest_framework import fields from . import styles class FlowStyleDictField(fields.DictField): """A DRF field which renders as a YAML flow style mapping.""" def to_representation(self, value: Any) -> dict[Any, Any]: """Render a flow style mapping.""" return styles.FlowStyleMapping(super().to_representation(value)) class FlowStyleListField(fields.ListField): """A DRF field which renders as a YAML flow style sequence.""" def to_representation(self, value: Any) -> list[Any]: """Render a flow style sequence.""" return styles.FlowStyleSequence(super().to_representation(value)) class SingleQuotedCharField(fields.CharField): """A DRF field which renders as a YAML single quoted string.""" def to_representation(self, value: Any) -> str: """Render a single quoted string.""" return styles.SingleQuotedStr(super().to_representation(value)) class DoubleQuotedCharField(fields.CharField): """A DRF field which renders as a YAML double quoted string.""" def to_representation(self, value: Any) -> str: """Render a double quoted string.""" return styles.DoubleQuotedStr(super().to_representation(value)) class FoldedCharField(fields.CharField): """A DRF field which renders as a YAML folded string.""" def to_representation(self, value: Any) -> str: """Render a folded string.""" return styles.FoldedStr(super().to_representation(value)) class LiteralCharField(fields.CharField): """A DRF field which renders as a YAML literal string.""" def to_representation(self, value: Any) -> str: """Render a literal string.""" return styles.LiteralStr(super().to_representation(value)) python-djangorestframework-yaml-3.0.1/drf_yaml/parsers.py000066400000000000000000000022551454654502300237120ustar00rootroot00000000000000"""Provides YAML parsing support.""" from typing import IO, Any, Dict, Mapping, Optional import yaml from django.conf import settings from django.utils.encoding import force_str from rest_framework.exceptions import ParseError from rest_framework.parsers import BaseParser class YAMLParser(BaseParser): """Parse YAML-serialized data.""" media_type = "application/yaml" # ruff: noqa: ARG002 def parse( self, stream: IO[Any], media_type: Optional[str] = None, parser_context: Optional[Mapping[str, Any]] = None, ) -> Dict[str, Any]: """Parse the incoming bytestream as YAML and returns the resulting data.""" parser_context = parser_context or {} encoding = parser_context.get("encoding", settings.DEFAULT_CHARSET) try: data = stream.read().decode(encoding) parsed_data = yaml.safe_load(data) except (ValueError, yaml.parser.ParserError) as exc: raise ParseError("YAML parse error - %s" % force_str(exc)) from exc if not isinstance(parsed_data, dict): raise ParseError("Expected dict, got %s" % type(parsed_data)) return parsed_data python-djangorestframework-yaml-3.0.1/drf_yaml/py.typed000066400000000000000000000000001454654502300233420ustar00rootroot00000000000000python-djangorestframework-yaml-3.0.1/drf_yaml/renderers.py000066400000000000000000000020241454654502300242160ustar00rootroot00000000000000"""Provides YAML rendering support.""" from typing import Any, Mapping, Optional import yaml from rest_framework.renderers import BaseRenderer from .encoders import SafeDumper class YAMLRenderer(BaseRenderer): """Renderer which serializes to YAML.""" media_type = "application/yaml" format = "yaml" encoder = SafeDumper charset = "utf-8" ensure_ascii = False default_flow_style = False sort_keys = False def render( self, data: Any, _accepted_media_type: Optional[str] = None, _renderer_context: Optional[Mapping[str, Any]] = None, ) -> bytes: """Render `data` into serialized YAML.""" if data is None: return b"" return yaml.dump( # type: ignore [no-any-return] data, sort_keys=self.sort_keys, stream=None, encoding=self.charset, Dumper=self.encoder, allow_unicode=not self.ensure_ascii, default_flow_style=self.default_flow_style, ) python-djangorestframework-yaml-3.0.1/drf_yaml/styles.py000066400000000000000000000012061454654502300235510ustar00rootroot00000000000000"""String types for YAML.""" class FoldedStr(str): """A string which should be rendered as a YAML folded string.""" class LiteralStr(str): """A string which should be rendered as a YAML literal string.""" class SingleQuotedStr(str): """A string which should be rendered as a YAML single quoted string.""" class DoubleQuotedStr(str): """A string which should be rendered as a YAML double quoted string.""" class FlowStyleSequence(list): """A sequence which should be rendered as a YAML flow style sequence.""" class FlowStyleMapping(dict): """A mapping which should be rendered as a YAML flow style mapping.""" python-djangorestframework-yaml-3.0.1/mkdocs.yml000066400000000000000000000013651454654502300220700ustar00rootroot00000000000000site_name: drf-yaml site_description: YAML support for Django REST Framework repo_url: https://github.com/Qu4tro/drf-yaml site_dir: html theme: name: material palette: - media: "(prefers-color-scheme)" # Palette toggle for automatic mode toggle: icon: material/brightness-auto name: Switch to light mode - media: "(prefers-color-scheme: light)" # Palette toggle for light mode scheme: default toggle: icon: material/brightness-7 name: Switch to dark mode - media: "(prefers-color-scheme: dark)" # Palette toggle for dark mode scheme: slate toggle: icon: material/brightness-4 name: Switch to system preference custom_dir: docs/overrides extra: version: provider: mike python-djangorestframework-yaml-3.0.1/pyproject.toml000066400000000000000000000045741454654502300230060ustar00rootroot00000000000000[tool.poetry] name = "drf-yaml" version = "3.0.1" description = "YAML support for Django REST Framework" authors = [ "José Padilla ", "Xavier Francisco ", ] license = "BSD" readme = "README.md" homepage = "https://github.com/Qu4tro/drf-yaml" classifiers = [ "Development Status :: 5 - Production/Stable", "Environment :: Web Environment", "Framework :: Django", "Intended Audience :: Developers", "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", "Natural Language :: English", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Topic :: Internet :: WWW/HTTP", ] packages = [{include = "drf_yaml"}] [tool.poetry.dependencies] python = "^3.8" PyYAML = ">=5.1" [tool.poetry.group.dev.dependencies] django = "^4.2" djangorestframework = "^3.14.0" tox = "^4.5.1" pre-commit = "^3.2.2" mypy = "^1.2.0" pytest = "^7.3.1" pytest-cov = "^4.0.0" pytest-sugar = "^0.9.7" djangorestframework-stubs = "^3.14.0" django-stubs = "^4.2.0" types-pyyaml = "^6.0.12.9" toml = "^0.10.2" bpython = "^0.24" types-toml = "^0.10.8.6" [tool.poetry.group.docs.dependencies] mkdocs = "^1.4.2" mkdocs-material = "^9.1.8" mike = "^1.1.2" [build-system] requires = ["poetry-core"] build-backend = "poetry.core.masonry.api" [tool.black] target-version = ["py311"] [tool.isort] profile = "black" [tool.mypy] strict = true [tool.ruff] select = [ "A" , "B", "C", "E", "D", "F", "G", "I", "N", "Q", "S", "T", "W", "ANN", "ARG", "BLE", "COM", "DJ", "DTZ", "EM", "ERA", "EXE", "FBT", "ICN", "INP", "ISC", "NPY", "PD", "PGH", "PIE", "PL", "PTH", "PYI", "RET", "RSE", "RUF", "SIM", "SLF", "TCH", "TID", "TRY", "UP", "YTT", ] ignore = [ "UP006", "UP007", "UP035", "ANN101", "ANN401", "D203", "D212", ] [tool.ruff.flake8-builtins] builtins-ignorelist = ['format'] [tool.pytest.ini_options] testpaths = ["tests"] python_files = [ "test_*.py", "_test_*.py", ] addopts = "--verbose -rf -rE --durations=8" [tool.coverage.run] source = ["."] branch = true omit = [] [tool.coverage.report] skip_covered = true skip_empty = true exclude_lines = [ "# pragma: no cover", "NotImplementedError", "if __name__ == \"__main__\"", ] python-djangorestframework-yaml-3.0.1/tests/000077500000000000000000000000001454654502300212225ustar00rootroot00000000000000python-djangorestframework-yaml-3.0.1/tests/__init__.py000066400000000000000000000000231454654502300233260ustar00rootroot00000000000000# ruff: noqa: D104 python-djangorestframework-yaml-3.0.1/tests/_test_dumper.py000066400000000000000000000324731454654502300242770ustar00rootroot00000000000000import textwrap from collections import OrderedDict from datetime import datetime, time from decimal import Decimal from uuid import UUID import yaml from django.test import TestCase from django.utils.safestring import SafeString from django.utils.timezone import utc from rest_framework.exceptions import ErrorDetail from rest_framework.relations import Hyperlink from rest_framework.utils.serializer_helpers import ReturnDict, ReturnList from drf_yaml import styles from drf_yaml.encoders import SafeDumper class _YAMLDumperTests(TestCase): """Tests specific to the YAML SafeDumper.""" def test_bytes(self) -> None: """ Test that Bytes are represented as YAML strings. Given: - A Bytes object - The YAML representation of that Bytes object as a string Do: - Render the Bytes object to YAML Expect: - The YAML string to be the same as the original YAML string """ _yaml_repr = "bytes: abcd\n" obj = {"bytes": b"abcd"} yaml_repr = yaml.dump( obj, sort_keys=False, Dumper=SafeDumper, default_flow_style=False, ) self.assertEqual(yaml_repr, _yaml_repr) def test_time(self) -> None: """ Test that Times are represented as YAML strings. Given: - A Time - The YAML representation of that Time as a string Do: - Render the Time to YAML Expect: - The YAML string to be the same as the original YAML string """ _yaml_repr = "time: '12:34:56'\n" obj = {"time": time(12, 34, 56)} yaml_repr = yaml.dump( obj, sort_keys=False, Dumper=SafeDumper, default_flow_style=False, ) self.assertEqual(yaml_repr, _yaml_repr) def test_timedelta(self) -> None: """ Test that Timedeltas are represented as YAML strings. Given: - A Timedelta - The YAML representation of that Timedelta as a string Do: - Render the Timedelta to YAML Expect: - The YAML string to be the same as the original YAML string """ _yaml_repr = "timedelta: 97440.0\n" obj = { "timedelta": datetime(1, 1, 2, 3, 4, tzinfo=utc) - datetime(1, 1, 1, tzinfo=utc), } yaml_repr = yaml.dump( obj, sort_keys=False, Dumper=SafeDumper, default_flow_style=False, ) self.assertEqual(yaml_repr, _yaml_repr) def test_decimal(self) -> None: """ Test that Decimals are represented as YAML strings. Given: - A Decimal - The YAML representation of that Decimal as a string Do: - Render the Decimal to YAML Expect: - The YAML string to be the same as the original YAML string """ _yaml_repr = "'123.456'\n" obj = Decimal("123.456") yaml_repr = yaml.dump( obj, sort_keys=False, Dumper=SafeDumper, default_flow_style=False, ) self.assertEqual(yaml_repr, _yaml_repr) def test_uuid(self) -> None: """ Test that UUIDs are represented as YAML strings. Given: - A UUID - The YAML representation of that UUID as a string Do: - Render the UUID to YAML Expect: - The YAML string to be the same as the original YAML string """ _yaml_repr = "uuid: 12345678-1234-5678-1234-567812345678\n" obj = {"uuid": UUID("12345678-1234-5678-1234-567812345678")} yaml_repr = yaml.dump( obj, sort_keys=False, Dumper=SafeDumper, default_flow_style=False, ) self.assertEqual(yaml_repr, _yaml_repr) def test_hyperlink(self) -> None: """ Test that Hyperlinks are represented as YAML strings. Given: - A Hyperlink - The YAML representation of that Hyperlink as a string Do: - Render the Hyperlink to YAML Expect: - The YAML string to be the same as the original YAML string """ _yaml_repr = "link: http://example.com\n" obj = {"link": Hyperlink("http://example.com", obj=None)} yaml_repr = yaml.dump( obj, sort_keys=False, Dumper=SafeDumper, default_flow_style=False, ) self.assertEqual(yaml_repr, _yaml_repr) def test_error_detail(self) -> None: """ Test that ErrorDetails are represented as YAML strings. Given: - An ErrorDetail - The YAML representation of that ErrorDetail as a string Do: - Render the ErrorDetail to YAML Expect: - The YAML string to be the same as the original YAML string """ _yaml_repr = "error: foo\n" obj = {"error": ErrorDetail("foo")} yaml_repr = yaml.dump( obj, sort_keys=False, Dumper=SafeDumper, default_flow_style=False, ) self.assertEqual(yaml_repr, _yaml_repr) def test_safe_string(self) -> None: """ Test that SafeStrings are represented as YAML strings. Given: - A SafeString - The YAML representation of that SafeString as a string Do: - Render the SafeString to YAML Expect: - The YAML string to be the same as the original YAML string """ _yaml_repr = "foo: bar\n" obj = {"foo": SafeString("bar")} yaml_repr = yaml.dump( obj, sort_keys=False, Dumper=SafeDumper, default_flow_style=False, ) self.assertEqual(yaml_repr, _yaml_repr) def test_ordered_dict(self) -> None: """ Test that OrderedDicts are represented as YAML mappings. Given: - A simple ordered dict - The YAML representation of that dict as a string Do: - Render the dict to YAML Expect: - The YAML string to be the same as the original YAML string """ _yaml_repr = "foo:\n- bar\n- baz\n" obj = OrderedDict({"foo": ["bar", "baz"]}) self.assertEqual( yaml.dump( obj, sort_keys=False, Dumper=SafeDumper, default_flow_style=False, ), _yaml_repr, ) def test_return_dict(self) -> None: """ Test that ReturnDicts are represented as YAML mappings. Given: - A ReturnDict - The YAML representation of that ReturnDict as a string Do: - Render the ReturnDict to YAML Expect: - The YAML string to be the same as the original YAML string """ _yaml_repr = "foo:\n- bar\n- baz\n" obj = ReturnDict({"foo": ["bar", "baz"]}, serializer=None) yaml_repr = yaml.dump( obj, sort_keys=False, Dumper=SafeDumper, default_flow_style=False, ) self.assertEqual(yaml_repr, _yaml_repr) def test_return_list(self) -> None: """ Test that ReturnLists are represented as YAML sequences. Given: - A ReturnList - The YAML representation of that ReturnList as a string Do: - Render the ReturnList to YAML Expect: - The YAML string to be the same as the original YAML string """ _yaml_repr = "- foo\n- bar\n- baz\n" obj = ReturnList(["foo", "bar", "baz"], serializer=None) yaml_repr = yaml.dump( obj, sort_keys=False, Dumper=SafeDumper, default_flow_style=False, ) self.assertEqual(yaml_repr, _yaml_repr) def test_generator_type(self) -> None: """ Test that GeneratorTypes are represented as YAML sequences. Given: - A GeneratorType - The YAML representation of that GeneratorType as a string Do: - Render the GeneratorType to YAML Expect: - The YAML string to be the same as the original YAML string """ _yaml_repr = "- foo\n- bar\n- baz\n" obj = (item for item in ["foo", "bar", "baz"]) yaml_repr = yaml.dump( obj, sort_keys=False, Dumper=SafeDumper, default_flow_style=False, ) self.assertEqual(yaml_repr, _yaml_repr) def test_folded_str(self) -> None: """ Test that folded strings are represented as YAML folded strings. Given: - A folded string - The YAML representation of that string as a string Do: - Render the string to YAML Expect: - The YAML string to be the same as the original YAML string """ _yaml_repr = "foo: >-\n bar\n\n baz\n" obj = {"foo": styles.FoldedStr("bar\nbaz")} yaml_repr = yaml.dump( obj, sort_keys=False, Dumper=SafeDumper, default_flow_style=False, ) self.assertEqual(yaml_repr, _yaml_repr) def test_literal_str(self) -> None: """ Test that literal strings are represented as YAML literal strings. Given: - A literal string - The YAML representation of that string as a string Do: - Render the string to YAML Expect: - The YAML string to be the same as the original YAML string """ _yaml_repr = "foo: |-\n bar\n baz\n" obj = {"foo": styles.LiteralStr("bar\nbaz")} yaml_repr = yaml.dump( obj, sort_keys=False, Dumper=SafeDumper, default_flow_style=False, ) self.assertEqual(yaml_repr, _yaml_repr) def test_single_quoted_str(self) -> None: """ Test that single quoted strings are represented as YAML single quoted strings. Given: - A single quoted string - The YAML representation of that string as a string Do: - Render the string to YAML Expect: - The YAML string to be the same as the original YAML string """ _yaml_repr = "foo: 'bar'\n" obj = {"foo": styles.SingleQuotedStr("bar")} yaml_repr = yaml.dump( obj, sort_keys=False, Dumper=SafeDumper, default_flow_style=False, ) self.assertEqual(yaml_repr, _yaml_repr) def test_double_quoted_str(self) -> None: """ Test that double quoted strings are represented as YAML double quoted strings. Given: - A double quoted string - The YAML representation of that string as a string Do: - Render the string to YAML Expect: - The YAML string to be the same as the original YAML string """ _yaml_repr = 'foo: "bar"\n' obj = {"foo": styles.DoubleQuotedStr("bar")} yaml_repr = yaml.dump( obj, sort_keys=False, Dumper=SafeDumper, default_flow_style=False, ) self.assertEqual(yaml_repr, _yaml_repr) def test_flow_style_sequence(self) -> None: """ Test that flow style sequences are represented as YAML flow style sequences. Given: - A flow style sequence - The YAML representation of that sequence as a string Do: - Render the sequence to YAML Expect: - The YAML string to be the same as the original YAML string """ _yaml_repr = textwrap.dedent( """ flowing: [bar, baz] not-flowing: - bar - baz """, ).lstrip() obj = { "flowing": styles.FlowStyleSequence(["bar", "baz"]), "not-flowing": ["bar", "baz"], } yaml_repr = yaml.dump( obj, sort_keys=False, Dumper=SafeDumper, default_flow_style=False, ) self.assertEqual(yaml_repr, _yaml_repr) def test_flow_style_mapping(self) -> None: """ Test that flow style mappings are represented as YAML flow style mappings. Given: - A flow style mapping - The YAML representation of that mapping as a string Do: - Render the mapping to YAML Expect: - The YAML string to be the same as the original YAML string """ _yaml_repr = textwrap.dedent( """ flowing: {foo: bar, baz: qux} not-flowing: foo: bar baz: qux """, ).lstrip() obj = { "flowing": styles.FlowStyleMapping({"foo": "bar", "baz": "qux"}), "not-flowing": {"foo": "bar", "baz": "qux"}, } yaml_repr = yaml.dump( obj, sort_keys=False, Dumper=SafeDumper, default_flow_style=False, ) self.assertEqual(yaml_repr, _yaml_repr) python-djangorestframework-yaml-3.0.1/tests/_test_renderers.py000066400000000000000000000064141454654502300247700ustar00rootroot00000000000000from decimal import Decimal from io import BytesIO from django.test import TestCase from rest_framework.relations import Hyperlink from drf_yaml.parsers import YAMLParser from drf_yaml.renderers import YAMLRenderer class _YAMLRendererTests(TestCase): """Tests specific to the YAML Renderer.""" def test_render(self) -> None: """ Rendering test. Given: - A simple dict with a list of strings - The YAML representation of that dict as a string Do: - Render the dict to YAML Expect: - The YAML string to be the same as the original YAML string """ _yaml_repr = "foo:\n- bar\n- baz\n" obj = {"foo": ["bar", "baz"]} renderer = YAMLRenderer() content = renderer.render(obj, "application/yaml") self.assertEqual(content.decode("utf-8"), _yaml_repr) def test_render_and_parse(self) -> None: """ Rendering and parsing test. Given: - A simple dict with a list of strings Do: - Render the dict to YAML - Parse the YAML back to a dict Expect: - The parsed dict to be the same as the original dict """ obj = {"foo": ["bar", "baz"]} renderer = YAMLRenderer() parser = YAMLParser() content = renderer.render(obj, "application/yaml") data = parser.parse(BytesIO(content)) self.assertEqual(obj, data) def test_render_decimal(self) -> None: """ YAML decimal rendering test. Given: - A dict with a decimal Do: - Render the dict to YAML Expect: - The YAML string to contain the decimal as a string """ renderer = YAMLRenderer() content = renderer.render( {"field": Decimal("111.2")}, "application/yaml", ) self.assertIn("field: '111.2'", content.decode("utf-8")) def test_render_hyperlink(self) -> None: """ YAML Hyperlink rendering test. Given: - A dict with a Hyperlink Do: - Render the dict to YAML Expect: - The YAML string to contain the Hyperlink as a string """ renderer = YAMLRenderer() content = renderer.render( {"field": Hyperlink("http://pépé.com?great-answer=42", "test")}, "application/yaml", ) self.assertIn( "field: http://pépé.com?great-answer=42", content.decode("utf-8"), ) def test_proper_encoding(self) -> None: """ YAML encoding test. Given: - A dict with a list of strings with non-ascii characters Do: - Render the dict to YAML Expect: - The YAML string to be the same as the original YAML string. - The YAML string to be encoded in utf-8 and contain the non-ascii characters. """ _yaml_repr = "countries:\n- United Kingdom\n- France\n- España" obj = {"countries": ["United Kingdom", "France", "España"]} renderer = YAMLRenderer() content = renderer.render(obj, "application/yaml") self.assertEqual(content.strip(), _yaml_repr.encode("utf-8")) python-djangorestframework-yaml-3.0.1/tests/_test_version.py000066400000000000000000000011361454654502300244600ustar00rootroot00000000000000from pathlib import Path import toml from django.test import TestCase import drf_yaml class _VersionTests(TestCase): """Tests specific to the package version.""" def test_versions_are_in_sync(self) -> None: """Ensure that the version on pyproject.toml and package.__init__.py match.""" path = Path(__file__).parent.parent / "pyproject.toml" pyproject = toml.loads(path.read_text()) pyproject_version = pyproject["tool"]["poetry"]["version"] package_init_version = drf_yaml.__version__ self.assertEqual(package_init_version, pyproject_version) python-djangorestframework-yaml-3.0.1/tests/conftest.py000066400000000000000000000011441454654502300234210ustar00rootroot00000000000000""" Pytest configuration file. This file is automatically loaded by pytest when running tests. See https://docs.pytest.org/en/latest/reference.html#configuration-options for more information. """ def pytest_configure() -> None: """Configure Django settings before tests run.""" from django.conf import settings settings.configure( DATABASES={ "default": { "ENGINE": "django.db.backends.sqlite3", "NAME": ":memory:", }, }, ) try: import django django.setup() except AttributeError: pass python-djangorestframework-yaml-3.0.1/tox.ini000066400000000000000000000015721454654502300214000ustar00rootroot00000000000000[tox] envlist = lint {py38,py39,py310}-django3.2-drf3.14 {py38,py39,py310,py311}-django4.2-drf3.14 docs isolated_build = True [gh-actions] python = 3.8: py38 3.9: py39 3.10: py310 3.11: py311, lint, docs [testenv] commands = pytest {posargs} deps = django3.2: Django==3.2.* django4.2: Django==4.2.* drf3.14: djangorestframework==3.14.* toml pytest [testenv:coverage] commands = pytest --verbose -rf -rE --cov=drf_yaml --cov-report=lcov --no-cov-on-fail --cov-branch {posargs} deps = django djangorestframework toml pytest pytest-cov [testenv:lint] basepython = python3.11 passenv = HOMEPATH # needed on Windows deps = pre-commit commands = pre-commit run --all-files [testenv:docs] basepython = python3.11 deps = mkdocs mkdocs-material commands = mkdocs build