saritasa-drf-tools


Namesaritasa-drf-tools JSON
Version 0.2.0 PyPI version JSON
download
home_pagehttps://pypi.org/project/saritasa-drf-tools/
SummaryTools For DRF Used By Saritasa
upload_time2025-09-08 08:43:44
maintainerStanislav Khlud
docs_urlNone
authorSaritasa
requires_python<4.0,>=3.12
licenseMIT
keywords python django drf
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # saritasa-drf-tools

![GitHub Workflow Status (with event)](https://img.shields.io/github/actions/workflow/status/saritasa-nest/saritasa-drf-tools/checks.yaml)
[![PyPI](https://img.shields.io/pypi/v/saritasa-drf-tools)](https://pypi.org/project/saritasa-drf-tools/)
![PyPI - Status](https://img.shields.io/pypi/status/saritasa-drf-tools)
![PyPI - Python Version](https://img.shields.io/pypi/pyversions/saritasa-drf-tools)
![PyPI - Django Version](https://img.shields.io/pypi/frameworkversions/django/saritasa-drf-tools)
![PyPI - License](https://img.shields.io/pypi/l/saritasa-drf-tools)
![PyPI - Downloads](https://img.shields.io/pypi/dm/saritasa-drf-tools)
[![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)

Tools For [DRF](https://www.django-rest-framework.org/) Used By Saritasa

## Table of contents

* [Installation](#installation)
* [Features](#features)
* [Optional dependencies](#optional-dependencies)
* [Serializers](#serializers)
* [Views](#views)
* [Pagination](#pagination)
* [Filters](#filters)
* [Renderers](#renderers)
* [OpenAPI](#openapi)
* [Tester](#tester)

## Installation

```bash
pip install saritasa-drf-tools
```

or if you are using [uv](https://docs.astral.sh/uv/)

```bash
uv add saritasa-drf-tools
```

or if you are using [poetry](https://python-poetry.org/)

```bash
poetry add saritasa-drf-tools
```

## Features

* Views - collection of mixins and viewsets classes
* Serializers - collection of mixins and serializers classes
* Filters - Custom filter backends that improve integration with
  [drf-spectacular](https://github.com/tfranzel/drf-spectacular)
* OpenAPI - tools for [drf-spectacular](https://github.com/tfranzel/drf-spectacular)
* `pytest` - plugin which provides different `api_client` fixtures.
* Testing classes(**Warning: Very experimental**) - Test class which contains shortcut to reduce boilerplate across tests.

For examples and to just check it out in action you can use [example folder](/example).

## Optional dependencies

* `[filters]` - Add this to enable `django-filters` support
* `[openapi]` - Add this to enable `drf-spectacular` support

## Views

### Views mixins

* `ActionPermissionsMixin`: Mixin which allows to define specific permissions per actions
  For example you have:

  ```python
  class CRUDView(
    ActionPermissionsMixin,
    ActionMixins, # Anything you need
    GenericViewSet,
  ):
    """CRUD view."""
    base_permission_classes = (permissions.AllowAny,)
    extra_permission_classes = (permissions.IsAuthenticated,)
    extra_permissions_map = {
        "create": (permissions.IsAdminUser,),
        "update": (permissions.IsAdminUser,),
        "destroy": (permissions.IsAdminUser,),
    }
  ```

  * `base_permission_classes` - Will be applied to any action (Usually you want this in base class of your project)
  * `extra_permission_classes` - Will be added to `base_permission_classes`
  * `extra_permission_map` - Will be added to (`base_permission_classes` + `extra_permission_classes`) on
    action you specify in mapping

  To learn more read class docs.

* `ActionSerializerMixin`: Mixin which allows to define specific serializers per action.
  For example you have

  ```python
  class CRUDView(
    ActionPermissionsMixin,
    ActionMixins, # Anything you need
    GenericViewSet,
  ):
    """CRUD view."""

    queryset = models.TestModel.objects.select_related("related_model").all()
    serializers_map = {
        "default": serializers.TestModelDetailSerializer,
        "list": serializers.TestModelListSerializer,
    }
  ```

  That means that on `list` view will use `TestModelListSerializer`, but on any other
  actions `TestModelDetailSerializer`. This will also will be reflected in
  generated openapi specs via [drf-spectacular](https://github.com/tfranzel/drf-spectacular)

  To learn more read class docs.

* `UpdateModelWithoutPatchMixin`: Same as UpdateModelMixin but without patch method

### Viewset classes

* `BaseViewSet`: Viewset with `ActionPermissionsMixin` and `ActionSerializerMixin`
* `CRUDViewSet`: Viewset with crud endpoint based on BaseViewSet
* `ReadOnlyViewSet`: Viewset with read endpoint based on BaseViewSet

## Pagination

* `LimitOffsetPagination`: Customized paginator class to limit max objects in list APIs.
  Use `SARITASA_DRF_MAX_PAGINATION_SIZE` to set default max for whole project.

## Serializers

### Serializers mixins

* `CleanValidationMixin`: Enable model `clean` validation in serializer
* `FieldMappingOverride`: Override or extend field mapping via `SARITASA_DRF_FIELD_MAPPING`.
  For example you can set following in settings.

  ```python
  SARITASA_DRF_FIELD_MAPPING = {
    "django.db.models.TextField": "example.app.api.fields.CustomCharField",
  }
  ```

  And now all `TextField` of your models will have `CustomCharField` in
  serializers.

* `UserAndRequestFromContextMixin`: Extracts user and request from context
  and sets it as attr of serializer instance.
* `NestedFieldsMixin`: Allows to define nested data fields for serializers via `Meta` class.

### Serializers classes

* `BaseSerializer`: Serializer with `UserAndRequestFromContextMixin`
* `ModelBaseSerializer`: ModelSerializer with `mixins.FieldMappingOverride`,
  `mixins.CleanValidationMixin`, `mixins.UserAndRequestFromContextMixin`,
  `mixins.NestedFieldsMixin`.

## Filters

Needs `filters` and `openapi` to be included to work properly.

* `OrderingFilterBackend`: Add supported fields to `ordering` param's description
  in specs generated by [drf-spectacular](https://github.com/tfranzel/drf-spectacular). Will raise warning specs validation
  on empty `ordering_fields` or if queryset is unable to order itself using `ordering_fields`.
  Example of description:

  ```text
  Which fields to use when ordering the results. A list fields separated by ,. Example: field1,field2

  Supported fields: id, text_field, related_model__text_field.

  To reverse order just add - to field. Example:field -> -field
  ```

  Also has support for `nulls_first` and `nulls_last` ordering.
  You can either set these options globally in your settings:

  ```python
  SARITASA_DRF_ORDERING_IS_NULL_FIRST = True
  SARITASA_DRF_ORDERING_IS_NULL_LAST = True
  ```

  Or you can set them per view:

  ```python
  class MyView(views.APIView):
      ordering_fields_extra_kwargs = {
          "my_field": {
              "nulls_first": True,
              "nulls_last": False,
          },
      }
  ```

* `SearchFilterBackend`: Add supported fields to `search` param's description
  in specs generated by [drf-spectacular](https://github.com/tfranzel/drf-spectacular). Will raise warning specs validation
  on empty `search_fields` or if queryset is unable to perform search using `search_fields`.

  Example of description:

  ```text
  A search term.

  Performed on this fields: text_field, related_model__text_field.
  ```

* `DjangoFilterBackend`: Customized `DjangoFilterBackend` to reduce queries count when viewing api requests via browser

## Renderers

* `BrowsableAPIRenderer`: Customization over drf's BrowsableAPIRenderer.
  With `SARITASA_DRF_BROWSABLE_API_ENABLE_HTML_FORM`(Default: `True`) or
  setting `enable_browsable_api_rendered_html_form`(If not present will use global setting)
  in view you can disable all extra forms which results in extra SQL queries.

## OpenAPI

Needs `openapi` to be included to work properly.

* `OpenApiSerializer`: Serializer that should be used for customizing open_api spec.
  Made to avoid warnings about unimplemented methods.
* `DetailSerializer`: To show in spec responses like this `{detail: text}`.
* `fix_api_view_warning`: Fix warning `This is graceful fallback handling for APIViews`.

## Pytest

Plugin provides following fixtures:

* `api_client_factory` - factory which generated `rest_framework.test.ApiClient` instance
* `api_client` - uses `api_client_factory` to generate `rest_framework.test.ApiClient` instance
* `user_api_client`(Needs `user` fixture) uses `api_client_factory` to generate `rest_framework.test.ApiClient` instance
  forces auth to `user`
* `admin_api_client`(Needs `admin` fixture) uses `api_client_factory` to generate `rest_framework.test.ApiClient` instance
  forces auth to `admin`

## Tester

**Warning**: Very experimental.

`saritasa_drf_tools.testing.ApiActionTester` - is a tester class which contains
fixtures and shortcuts to simply and reduce boilerplate in tests for viewsets.

All you need to is create `tester.py`(you what ever you want it's just recommendation).
In this file declare new class which inherits `ApiActionTester`.

```python
class CRUDApiActionTester(
    saritasa_drf_tools.testing.ApiActionTester.init_subclass(
        model=models.TestModel, # Model of queryset in viewset
        user_model=models.User, # Model of user used across project
        factory=factories.TestModelFactory, # Factory which is used to generate instances for model
        api_view=api.views.CRUDView, # Class of viewset against which we will be writing tests
        url_basename="crud-api", # Base name of urls of viewset. {url_basename}-{action}
    ),
):
    """Tester for crud API."""
```

Next you can write test just like this. (For more examples check this [folder](tests/test_crud_api))

```python
class TestCRUD(tester.CRUDApiActionTester):
    """Define tests."""

    @pytest.mark.parametrize(
        argnames=[
            "parametrize_user",
            "status_code",
        ],
        argvalues=[
            [
                None,
                status.HTTP_403_FORBIDDEN,
            ],
            [
                pytest_lazy_fixtures.lf("user"),
                status.HTTP_403_FORBIDDEN,
            ],
            [
                pytest_lazy_fixtures.lf("admin"),
                status.HTTP_201_CREATED,
            ],
        ],
    )
    def test_permission_map_specified_action_create(
        self,
        instance: tester.CRUDApiActionTester.model,
        parametrize_user: tester.CRUDApiActionTester.user_model | None,
        status_code: int,
    ) -> None:
        """Test that create action will properly handle permissions."""
        self.make_request(
            method="post",
            user=parametrize_user,
            expected_status=status_code,
            path=self.lazy_url(action="list"),
            data=self.serialize_data(action="create", data=instance),
        )
```

            

Raw data

            {
    "_id": null,
    "home_page": "https://pypi.org/project/saritasa-drf-tools/",
    "name": "saritasa-drf-tools",
    "maintainer": "Stanislav Khlud",
    "docs_url": null,
    "requires_python": "<4.0,>=3.12",
    "maintainer_email": "stanislav.khlud@saritasa.com",
    "keywords": "python, django, drf",
    "author": "Saritasa",
    "author_email": "pypi@saritasa.com",
    "download_url": "https://files.pythonhosted.org/packages/7e/1b/8fa1c724b63454688dfb6484937c15f1b190a45713a31588dc3f89c206b9/saritasa_drf_tools-0.2.0.tar.gz",
    "platform": null,
    "description": "# saritasa-drf-tools\n\n![GitHub Workflow Status (with event)](https://img.shields.io/github/actions/workflow/status/saritasa-nest/saritasa-drf-tools/checks.yaml)\n[![PyPI](https://img.shields.io/pypi/v/saritasa-drf-tools)](https://pypi.org/project/saritasa-drf-tools/)\n![PyPI - Status](https://img.shields.io/pypi/status/saritasa-drf-tools)\n![PyPI - Python Version](https://img.shields.io/pypi/pyversions/saritasa-drf-tools)\n![PyPI - Django Version](https://img.shields.io/pypi/frameworkversions/django/saritasa-drf-tools)\n![PyPI - License](https://img.shields.io/pypi/l/saritasa-drf-tools)\n![PyPI - Downloads](https://img.shields.io/pypi/dm/saritasa-drf-tools)\n[![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)\n\nTools For [DRF](https://www.django-rest-framework.org/) Used By Saritasa\n\n## Table of contents\n\n* [Installation](#installation)\n* [Features](#features)\n* [Optional dependencies](#optional-dependencies)\n* [Serializers](#serializers)\n* [Views](#views)\n* [Pagination](#pagination)\n* [Filters](#filters)\n* [Renderers](#renderers)\n* [OpenAPI](#openapi)\n* [Tester](#tester)\n\n## Installation\n\n```bash\npip install saritasa-drf-tools\n```\n\nor if you are using [uv](https://docs.astral.sh/uv/)\n\n```bash\nuv add saritasa-drf-tools\n```\n\nor if you are using [poetry](https://python-poetry.org/)\n\n```bash\npoetry add saritasa-drf-tools\n```\n\n## Features\n\n* Views - collection of mixins and viewsets classes\n* Serializers - collection of mixins and serializers classes\n* Filters - Custom filter backends that improve integration with\n  [drf-spectacular](https://github.com/tfranzel/drf-spectacular)\n* OpenAPI - tools for [drf-spectacular](https://github.com/tfranzel/drf-spectacular)\n* `pytest` - plugin which provides different `api_client` fixtures.\n* Testing classes(**Warning: Very experimental**) - Test class which contains shortcut to reduce boilerplate across tests.\n\nFor examples and to just check it out in action you can use [example folder](/example).\n\n## Optional dependencies\n\n* `[filters]` - Add this to enable `django-filters` support\n* `[openapi]` - Add this to enable `drf-spectacular` support\n\n## Views\n\n### Views mixins\n\n* `ActionPermissionsMixin`: Mixin which allows to define specific permissions per actions\n  For example you have:\n\n  ```python\n  class CRUDView(\n    ActionPermissionsMixin,\n    ActionMixins, # Anything you need\n    GenericViewSet,\n  ):\n    \"\"\"CRUD view.\"\"\"\n    base_permission_classes = (permissions.AllowAny,)\n    extra_permission_classes = (permissions.IsAuthenticated,)\n    extra_permissions_map = {\n        \"create\": (permissions.IsAdminUser,),\n        \"update\": (permissions.IsAdminUser,),\n        \"destroy\": (permissions.IsAdminUser,),\n    }\n  ```\n\n  * `base_permission_classes` - Will be applied to any action (Usually you want this in base class of your project)\n  * `extra_permission_classes` - Will be added to `base_permission_classes`\n  * `extra_permission_map` - Will be added to (`base_permission_classes` + `extra_permission_classes`) on\n    action you specify in mapping\n\n  To learn more read class docs.\n\n* `ActionSerializerMixin`: Mixin which allows to define specific serializers per action.\n  For example you have\n\n  ```python\n  class CRUDView(\n    ActionPermissionsMixin,\n    ActionMixins, # Anything you need\n    GenericViewSet,\n  ):\n    \"\"\"CRUD view.\"\"\"\n\n    queryset = models.TestModel.objects.select_related(\"related_model\").all()\n    serializers_map = {\n        \"default\": serializers.TestModelDetailSerializer,\n        \"list\": serializers.TestModelListSerializer,\n    }\n  ```\n\n  That means that on `list` view will use `TestModelListSerializer`, but on any other\n  actions `TestModelDetailSerializer`. This will also will be reflected in\n  generated openapi specs via [drf-spectacular](https://github.com/tfranzel/drf-spectacular)\n\n  To learn more read class docs.\n\n* `UpdateModelWithoutPatchMixin`: Same as UpdateModelMixin but without patch method\n\n### Viewset classes\n\n* `BaseViewSet`: Viewset with `ActionPermissionsMixin` and `ActionSerializerMixin`\n* `CRUDViewSet`: Viewset with crud endpoint based on BaseViewSet\n* `ReadOnlyViewSet`: Viewset with read endpoint based on BaseViewSet\n\n## Pagination\n\n* `LimitOffsetPagination`: Customized paginator class to limit max objects in list APIs.\n  Use `SARITASA_DRF_MAX_PAGINATION_SIZE` to set default max for whole project.\n\n## Serializers\n\n### Serializers mixins\n\n* `CleanValidationMixin`: Enable model `clean` validation in serializer\n* `FieldMappingOverride`: Override or extend field mapping via `SARITASA_DRF_FIELD_MAPPING`.\n  For example you can set following in settings.\n\n  ```python\n  SARITASA_DRF_FIELD_MAPPING = {\n    \"django.db.models.TextField\": \"example.app.api.fields.CustomCharField\",\n  }\n  ```\n\n  And now all `TextField` of your models will have `CustomCharField` in\n  serializers.\n\n* `UserAndRequestFromContextMixin`: Extracts user and request from context\n  and sets it as attr of serializer instance.\n* `NestedFieldsMixin`: Allows to define nested data fields for serializers via `Meta` class.\n\n### Serializers classes\n\n* `BaseSerializer`: Serializer with `UserAndRequestFromContextMixin`\n* `ModelBaseSerializer`: ModelSerializer with `mixins.FieldMappingOverride`,\n  `mixins.CleanValidationMixin`, `mixins.UserAndRequestFromContextMixin`,\n  `mixins.NestedFieldsMixin`.\n\n## Filters\n\nNeeds `filters` and `openapi` to be included to work properly.\n\n* `OrderingFilterBackend`: Add supported fields to `ordering` param's description\n  in specs generated by [drf-spectacular](https://github.com/tfranzel/drf-spectacular). Will raise warning specs validation\n  on empty `ordering_fields` or if queryset is unable to order itself using `ordering_fields`.\n  Example of description:\n\n  ```text\n  Which fields to use when ordering the results. A list fields separated by ,. Example: field1,field2\n\n  Supported fields: id, text_field, related_model__text_field.\n\n  To reverse order just add - to field. Example:field -> -field\n  ```\n\n  Also has support for `nulls_first` and `nulls_last` ordering.\n  You can either set these options globally in your settings:\n\n  ```python\n  SARITASA_DRF_ORDERING_IS_NULL_FIRST = True\n  SARITASA_DRF_ORDERING_IS_NULL_LAST = True\n  ```\n\n  Or you can set them per view:\n\n  ```python\n  class MyView(views.APIView):\n      ordering_fields_extra_kwargs = {\n          \"my_field\": {\n              \"nulls_first\": True,\n              \"nulls_last\": False,\n          },\n      }\n  ```\n\n* `SearchFilterBackend`: Add supported fields to `search` param's description\n  in specs generated by [drf-spectacular](https://github.com/tfranzel/drf-spectacular). Will raise warning specs validation\n  on empty `search_fields` or if queryset is unable to perform search using `search_fields`.\n\n  Example of description:\n\n  ```text\n  A search term.\n\n  Performed on this fields: text_field, related_model__text_field.\n  ```\n\n* `DjangoFilterBackend`: Customized `DjangoFilterBackend` to reduce queries count when viewing api requests via browser\n\n## Renderers\n\n* `BrowsableAPIRenderer`: Customization over drf's BrowsableAPIRenderer.\n  With `SARITASA_DRF_BROWSABLE_API_ENABLE_HTML_FORM`(Default: `True`) or\n  setting `enable_browsable_api_rendered_html_form`(If not present will use global setting)\n  in view you can disable all extra forms which results in extra SQL queries.\n\n## OpenAPI\n\nNeeds `openapi` to be included to work properly.\n\n* `OpenApiSerializer`: Serializer that should be used for customizing open_api spec.\n  Made to avoid warnings about unimplemented methods.\n* `DetailSerializer`: To show in spec responses like this `{detail: text}`.\n* `fix_api_view_warning`: Fix warning `This is graceful fallback handling for APIViews`.\n\n## Pytest\n\nPlugin provides following fixtures:\n\n* `api_client_factory` - factory which generated `rest_framework.test.ApiClient` instance\n* `api_client` - uses `api_client_factory` to generate `rest_framework.test.ApiClient` instance\n* `user_api_client`(Needs `user` fixture) uses `api_client_factory` to generate `rest_framework.test.ApiClient` instance\n  forces auth to `user`\n* `admin_api_client`(Needs `admin` fixture) uses `api_client_factory` to generate `rest_framework.test.ApiClient` instance\n  forces auth to `admin`\n\n## Tester\n\n**Warning**: Very experimental.\n\n`saritasa_drf_tools.testing.ApiActionTester` - is a tester class which contains\nfixtures and shortcuts to simply and reduce boilerplate in tests for viewsets.\n\nAll you need to is create `tester.py`(you what ever you want it's just recommendation).\nIn this file declare new class which inherits `ApiActionTester`.\n\n```python\nclass CRUDApiActionTester(\n    saritasa_drf_tools.testing.ApiActionTester.init_subclass(\n        model=models.TestModel, # Model of queryset in viewset\n        user_model=models.User, # Model of user used across project\n        factory=factories.TestModelFactory, # Factory which is used to generate instances for model\n        api_view=api.views.CRUDView, # Class of viewset against which we will be writing tests\n        url_basename=\"crud-api\", # Base name of urls of viewset. {url_basename}-{action}\n    ),\n):\n    \"\"\"Tester for crud API.\"\"\"\n```\n\nNext you can write test just like this. (For more examples check this [folder](tests/test_crud_api))\n\n```python\nclass TestCRUD(tester.CRUDApiActionTester):\n    \"\"\"Define tests.\"\"\"\n\n    @pytest.mark.parametrize(\n        argnames=[\n            \"parametrize_user\",\n            \"status_code\",\n        ],\n        argvalues=[\n            [\n                None,\n                status.HTTP_403_FORBIDDEN,\n            ],\n            [\n                pytest_lazy_fixtures.lf(\"user\"),\n                status.HTTP_403_FORBIDDEN,\n            ],\n            [\n                pytest_lazy_fixtures.lf(\"admin\"),\n                status.HTTP_201_CREATED,\n            ],\n        ],\n    )\n    def test_permission_map_specified_action_create(\n        self,\n        instance: tester.CRUDApiActionTester.model,\n        parametrize_user: tester.CRUDApiActionTester.user_model | None,\n        status_code: int,\n    ) -> None:\n        \"\"\"Test that create action will properly handle permissions.\"\"\"\n        self.make_request(\n            method=\"post\",\n            user=parametrize_user,\n            expected_status=status_code,\n            path=self.lazy_url(action=\"list\"),\n            data=self.serialize_data(action=\"create\", data=instance),\n        )\n```\n",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "Tools For DRF Used By Saritasa",
    "version": "0.2.0",
    "project_urls": {
        "Homepage": "https://pypi.org/project/saritasa-drf-tools/",
        "Repository": "https://github.com/saritasa-nest/saritasa-drf-tools/"
    },
    "split_keywords": [
        "python",
        " django",
        " drf"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "0e6beecbd4c111eba3b4a6f3afa5a53177dbcb0e703b4948adc1c6841fa993d9",
                "md5": "22f1f493843d3127ab55e656e8f8759e",
                "sha256": "2bffcc15345ed32a66b7036a2326edc47418f83fcb8214d682b0974019285f70"
            },
            "downloads": -1,
            "filename": "saritasa_drf_tools-0.2.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "22f1f493843d3127ab55e656e8f8759e",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": "<4.0,>=3.12",
            "size": 26046,
            "upload_time": "2025-09-08T08:43:42",
            "upload_time_iso_8601": "2025-09-08T08:43:42.812111Z",
            "url": "https://files.pythonhosted.org/packages/0e/6b/eecbd4c111eba3b4a6f3afa5a53177dbcb0e703b4948adc1c6841fa993d9/saritasa_drf_tools-0.2.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "7e1b8fa1c724b63454688dfb6484937c15f1b190a45713a31588dc3f89c206b9",
                "md5": "4b0c73f1e2e74db5548f5caa1450c1a0",
                "sha256": "a48479e69a9a515eb5b92af87ee96a72200bcd523da7760b63bb3cb4b7ed6ef8"
            },
            "downloads": -1,
            "filename": "saritasa_drf_tools-0.2.0.tar.gz",
            "has_sig": false,
            "md5_digest": "4b0c73f1e2e74db5548f5caa1450c1a0",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": "<4.0,>=3.12",
            "size": 20887,
            "upload_time": "2025-09-08T08:43:44",
            "upload_time_iso_8601": "2025-09-08T08:43:44.049669Z",
            "url": "https://files.pythonhosted.org/packages/7e/1b/8fa1c724b63454688dfb6484937c15f1b190a45713a31588dc3f89c206b9/saritasa_drf_tools-0.2.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2025-09-08 08:43:44",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "saritasa-nest",
    "github_project": "saritasa-drf-tools",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "lcname": "saritasa-drf-tools"
}
        
Elapsed time: 0.58293s