drf-aggregation


Namedrf-aggregation JSON
Version 2.1.0 PyPI version JSON
download
home_pageNone
SummaryGet DB aggregations using Django ORM
upload_time2024-10-15 20:01:37
maintainerNone
docs_urlNone
authorNone
requires_python>=3.8
licenseMIT License Copyright (c) 2020 Nikita Balobanov 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.
keywords django aggregation django-rest-framework django-orm
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # Django Rest Framework Aggregation


[![codecov badge](https://codecov.io/gh/kit-oz/drf-aggregation/branch/main/graph/badge.svg?token=X1RWDJI9NG)](https://codecov.io/gh/kit-oz/drf-aggregation)


DRF Mixin for getting aggregations

Key features:

- can get multiple aggregations at once
- can calculate percentile and percent (must be enabled separately)
- grouping by multiple fields
- time series (except SQLite)
- limiting the number of displayed records


## Installing

For installing use pip

```bash
    pip install drf-aggregation
```


## Usage

### Register mixin

The simplest variant of usage is to create a ViewSet with the provided mixin

```python
from drf_aggregation import AggregationMixin


class TicketViewSet(AggregationMixin, GenericViewSet):
    queryset = Ticket.objects.all()
    serializer_class = TicketSerializer

urlpatterns = [
    path("aggregation/ticket", TicketViewSet.as_view({"post": "aggregation"})),
]
```

After that you can use it

```http request
POST /aggregation/ticket
Content-Type: application/json
{
    "group_by": "service",
    "limit": 5,
    "order_by": "-total_tasks",
    "aggregations": {
        "total_tasks": {
            "type": "count"
        },
        "average_execution_time": {
            "type": "average",
            "field": "execution_time",
        }
    }
}
```


### Usage in code

Almost all a mixin does is call a function that you can use directly at your way

```python
from drf_aggregation import get_aggregations

result = get_aggregations(
    queryset=Ticket.objects.all(),
    aggregations={
        "total_tasks": {
            "type": "count"
        },
    }
)
```


### Available params

- aggregations - dictionary with aggregations to obtain
    - key - the key under which the aggregation result will be returned
    - value - dictionary with aggregation settings
        - type - aggregation type
        - index_by_group - add an index relative to the specified field for further sorting by it
        - field - required for aggregations: sum, average, minimum, maximum, percentile
        - percentile - from 0 to 1, required for percentile
        - additional_filter - filter parser is used from package [drf-complex-filter](https://github.com/kit-oz/drf-complex-filter), required for percent
- group_by - list of fields to group the result
- order_by - list of fields to sort the result
- limit - number of groups to return or dictionary with settings:
    - limit - number of groups to return
    - offset - shift start of returned groups
    - by_group - which group to limit the result by, by default - the first field for grouping
    - by_aggregation - which aggregation to limit the result by, by default - the first declared aggregation
    - show_other - return the remaining records as one additional group
    - other_label - label of additional group with recordings beyond the limit


### Supported field types

- IntegerField
- FloatField
- DateField (only minimum and maximum)
- DateTimeField (only minimum and maximum)
- DurationField


## Extend aggregation types

By default, only these aggregations are enabled: count, distinct, sum, average, minimum, maximum

Package provide two more aggregations - percent and percentile. But to use them, you need to enable them manually:

```python
# in settings.py
DRF_AGGREGATION_SETTINGS = {
    "AGGREGATION_CLASSES": [
        "drf_aggregation.aggregations.common.CommonAggregations",

        # need to install additional package "drf-complex-filter"
        "drf_aggregation.aggregations.percent.PercentAggregation",

        # works only on PostgreSQL
        "drf_aggregation.aggregations.percentile.PercentileAggregation",
    ],
}
```

You can also create your own aggregations. To do this, create a class with static methods that will be available as an aggregation type

```python
class MyAwesomeAggregations:
    @staticmethod
    def my_aggregation(aggregation: Aggregation, queryset: models.QuerySet):
        name = aggregation.get("name")
        return {f"{name}": models.Count("id")}

# in settings.py
DRF_AGGREGATION_SETTINGS = {
    "AGGREGATION_CLASSES": [
        "drf_aggregation.aggregations.common.CommonAggregations",
        "path.to.MyAwesomeAggregations",
    ],
}

result = get_aggregations(
    queryset=Ticket.objects.all(),
    aggregations={
        "value": {
            "type": "my_aggregation"
        },
    }
)
```


## Usage examples


### Grouping results

To group the result, a comma-separated list of required fields is passed

```python
result = get_aggregations(
    queryset=Ticket.objects.all(),
    aggregations={
        "total_tasks": {
            "type": "count"
        },
    },
    group_by=["field1", "field2"]
)
```


## Sorting the result

When grouping by one field, it is enough to pass a list of fields by which you need to sort the result

```python
result = get_aggregations(
    queryset=Ticket.objects.all(),
    aggregations={
        "total_tasks": {
            "type": "count"
        },
    },
    group_by="field1",
    order_by="field1"
)
```

The requested aggregations can be used as a sorting key

```python
result = get_aggregations(
    queryset=Ticket.objects.all(),
    aggregations={
        "total_tasks": {
            "type": "count"
        },
    },
    group_by="field1",
    order_by="-total_tasks"
)
```

When grouping by multiple fields, you can add an index for the desired group and aggregation pair, after which you can use this index for sorting.

```python
result = get_aggregations(
    queryset=Ticket.objects.all(),
    aggregations={
        "total_tasks": {
            "type": "count",
            "index_by_group": "field1"
        },
    },
    group_by=["field1", "field2"],
    order_by="-field1__total_tasks"
)
```


## Limiting the number of displayed groups

If you have a large number of categories or you need to display only top-N, it is possible to limit the number of returned records

```python
result = get_aggregations(
    queryset=Ticket.objects.all(),
    aggregations={
        "total_tasks": {
            "type": "count",
        },
    },
    group_by="field1",
    order_by="-total_tasks",
    limit=2
)
```

It is also possible to display all other groups as one additional category

```python
result = get_aggregations(
    queryset=Ticket.objects.all(),
    aggregations={
        "total_tasks": {
            "type": "count",
        },
    },
    group_by="field1",
    order_by="-total_tasks",
    limit={
        "limit": 2,
        "show_other": true
    }
)
```

Other parameters to limit:

- by_group - field for selecting the values that will remain, if not passed, the first field for grouping is used
- by_aggregation
- show_other - if true, all groups not included in the top will be displayed as one additional category
- other_label - label for additional category, default "Other"

## Time series

Warning! Doesn't work on SQLite because it doesn't have date / time fields.

To get an aggregation for a time series, you must first annotate your queryset with a truncated date field, and then use that field for grouping.

```python
truncate_rules = { "created_at": "day" }
queryset = truncate_date(Ticket.objects.all(), truncate_rules)

result = get_aggregations(
    queryset=queryset,
    aggregations={
        "total_tasks": {
            "type": "count",
        },
    },
    group_by="created_at__trunc__day",
)
```

If you use AggregationMixin, you just need to pass truncate_rules in the request body.

```http request
POST /aggregation/ticket
Content-Type: application/json
{
    "truncate_rules": { "created_at": "day" },
    "group_by": "created_at__trunc__day",
    "aggregations": {
        "total_tasks": {
            "type": "count"
        },
    }
}
```

Available truncations:

- year
- quarter
- month
- week
- day
- hour
- minute
- second


For mo details about truncations read [Django Docs](https://docs.djangoproject.com/en/3.1/ref/models/database-functions/#trunc)

            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "drf-aggregation",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.8",
    "maintainer_email": null,
    "keywords": "django, aggregation, django-rest-framework, django-orm",
    "author": null,
    "author_email": "Nikita Balobanov <kit-oz@ya.ru>",
    "download_url": "https://files.pythonhosted.org/packages/ea/18/4b9ade971084b578756b7b354549aa16eeede04d0e612fea0ab8e8ffd938/drf_aggregation-2.1.0.tar.gz",
    "platform": null,
    "description": "# Django Rest Framework Aggregation\n\n\n[![codecov badge](https://codecov.io/gh/kit-oz/drf-aggregation/branch/main/graph/badge.svg?token=X1RWDJI9NG)](https://codecov.io/gh/kit-oz/drf-aggregation)\n\n\nDRF Mixin for getting aggregations\n\nKey features:\n\n- can get multiple aggregations at once\n- can calculate percentile and percent (must be enabled separately)\n- grouping by multiple fields\n- time series (except SQLite)\n- limiting the number of displayed records\n\n\n## Installing\n\nFor installing use pip\n\n```bash\n    pip install drf-aggregation\n```\n\n\n## Usage\n\n### Register mixin\n\nThe simplest variant of usage is to create a ViewSet with the provided mixin\n\n```python\nfrom drf_aggregation import AggregationMixin\n\n\nclass TicketViewSet(AggregationMixin, GenericViewSet):\n    queryset = Ticket.objects.all()\n    serializer_class = TicketSerializer\n\nurlpatterns = [\n    path(\"aggregation/ticket\", TicketViewSet.as_view({\"post\": \"aggregation\"})),\n]\n```\n\nAfter that you can use it\n\n```http request\nPOST /aggregation/ticket\nContent-Type: application/json\n{\n    \"group_by\": \"service\",\n    \"limit\": 5,\n    \"order_by\": \"-total_tasks\",\n    \"aggregations\": {\n        \"total_tasks\": {\n            \"type\": \"count\"\n        },\n        \"average_execution_time\": {\n            \"type\": \"average\",\n            \"field\": \"execution_time\",\n        }\n    }\n}\n```\n\n\n### Usage in code\n\nAlmost all a mixin does is call a function that you can use directly at your way\n\n```python\nfrom drf_aggregation import get_aggregations\n\nresult = get_aggregations(\n    queryset=Ticket.objects.all(),\n    aggregations={\n        \"total_tasks\": {\n            \"type\": \"count\"\n        },\n    }\n)\n```\n\n\n### Available params\n\n- aggregations - dictionary with aggregations to obtain\n    - key - the key under which the aggregation result will be returned\n    - value - dictionary with aggregation settings\n        - type - aggregation type\n        - index_by_group - add an index relative to the specified field for further sorting by it\n        - field - required for aggregations: sum, average, minimum, maximum, percentile\n        - percentile - from 0 to 1, required for percentile\n        - additional_filter - filter parser is used from package [drf-complex-filter](https://github.com/kit-oz/drf-complex-filter), required for percent\n- group_by - list of fields to group the result\n- order_by - list of fields to sort the result\n- limit - number of groups to return or dictionary with settings:\n    - limit - number of groups to return\n    - offset - shift start of returned groups\n    - by_group - which group to limit the result by, by default - the first field for grouping\n    - by_aggregation - which aggregation to limit the result by, by default - the first declared aggregation\n    - show_other - return the remaining records as one additional group\n    - other_label - label of additional group with recordings beyond the limit\n\n\n### Supported field types\n\n- IntegerField\n- FloatField\n- DateField (only minimum and maximum)\n- DateTimeField (only minimum and maximum)\n- DurationField\n\n\n## Extend aggregation types\n\nBy default, only these aggregations are enabled: count, distinct, sum, average, minimum, maximum\n\nPackage provide two more aggregations - percent and percentile. But to use them, you need to enable them manually:\n\n```python\n# in settings.py\nDRF_AGGREGATION_SETTINGS = {\n    \"AGGREGATION_CLASSES\": [\n        \"drf_aggregation.aggregations.common.CommonAggregations\",\n\n        # need to install additional package \"drf-complex-filter\"\n        \"drf_aggregation.aggregations.percent.PercentAggregation\",\n\n        # works only on PostgreSQL\n        \"drf_aggregation.aggregations.percentile.PercentileAggregation\",\n    ],\n}\n```\n\nYou can also create your own aggregations. To do this, create a class with static methods that will be available as an aggregation type\n\n```python\nclass MyAwesomeAggregations:\n    @staticmethod\n    def my_aggregation(aggregation: Aggregation, queryset: models.QuerySet):\n        name = aggregation.get(\"name\")\n        return {f\"{name}\": models.Count(\"id\")}\n\n# in settings.py\nDRF_AGGREGATION_SETTINGS = {\n    \"AGGREGATION_CLASSES\": [\n        \"drf_aggregation.aggregations.common.CommonAggregations\",\n        \"path.to.MyAwesomeAggregations\",\n    ],\n}\n\nresult = get_aggregations(\n    queryset=Ticket.objects.all(),\n    aggregations={\n        \"value\": {\n            \"type\": \"my_aggregation\"\n        },\n    }\n)\n```\n\n\n## Usage examples\n\n\n### Grouping results\n\nTo group the result, a comma-separated list of required fields is passed\n\n```python\nresult = get_aggregations(\n    queryset=Ticket.objects.all(),\n    aggregations={\n        \"total_tasks\": {\n            \"type\": \"count\"\n        },\n    },\n    group_by=[\"field1\", \"field2\"]\n)\n```\n\n\n## Sorting the result\n\nWhen grouping by one field, it is enough to pass a list of fields by which you need to sort the result\n\n```python\nresult = get_aggregations(\n    queryset=Ticket.objects.all(),\n    aggregations={\n        \"total_tasks\": {\n            \"type\": \"count\"\n        },\n    },\n    group_by=\"field1\",\n    order_by=\"field1\"\n)\n```\n\nThe requested aggregations can be used as a sorting key\n\n```python\nresult = get_aggregations(\n    queryset=Ticket.objects.all(),\n    aggregations={\n        \"total_tasks\": {\n            \"type\": \"count\"\n        },\n    },\n    group_by=\"field1\",\n    order_by=\"-total_tasks\"\n)\n```\n\nWhen grouping by multiple fields, you can add an index for the desired group and aggregation pair, after which you can use this index for sorting.\n\n```python\nresult = get_aggregations(\n    queryset=Ticket.objects.all(),\n    aggregations={\n        \"total_tasks\": {\n            \"type\": \"count\",\n            \"index_by_group\": \"field1\"\n        },\n    },\n    group_by=[\"field1\", \"field2\"],\n    order_by=\"-field1__total_tasks\"\n)\n```\n\n\n## Limiting the number of displayed groups\n\nIf you have a large number of categories or you need to display only top-N, it is possible to limit the number of returned records\n\n```python\nresult = get_aggregations(\n    queryset=Ticket.objects.all(),\n    aggregations={\n        \"total_tasks\": {\n            \"type\": \"count\",\n        },\n    },\n    group_by=\"field1\",\n    order_by=\"-total_tasks\",\n    limit=2\n)\n```\n\nIt is also possible to display all other groups as one additional category\n\n```python\nresult = get_aggregations(\n    queryset=Ticket.objects.all(),\n    aggregations={\n        \"total_tasks\": {\n            \"type\": \"count\",\n        },\n    },\n    group_by=\"field1\",\n    order_by=\"-total_tasks\",\n    limit={\n        \"limit\": 2,\n        \"show_other\": true\n    }\n)\n```\n\nOther parameters to limit:\n\n- by_group - field for selecting the values that will remain, if not passed, the first field for grouping is used\n- by_aggregation\n- show_other - if true, all groups not included in the top will be displayed as one additional category\n- other_label - label for additional category, default \"Other\"\n\n## Time series\n\nWarning! Doesn't work on SQLite because it doesn't have date / time fields.\n\nTo get an aggregation for a time series, you must first annotate your queryset with a truncated date field, and then use that field for grouping.\n\n```python\ntruncate_rules = { \"created_at\": \"day\" }\nqueryset = truncate_date(Ticket.objects.all(), truncate_rules)\n\nresult = get_aggregations(\n    queryset=queryset,\n    aggregations={\n        \"total_tasks\": {\n            \"type\": \"count\",\n        },\n    },\n    group_by=\"created_at__trunc__day\",\n)\n```\n\nIf you use AggregationMixin, you just need to pass truncate_rules in the request body.\n\n```http request\nPOST /aggregation/ticket\nContent-Type: application/json\n{\n    \"truncate_rules\": { \"created_at\": \"day\" },\n    \"group_by\": \"created_at__trunc__day\",\n    \"aggregations\": {\n        \"total_tasks\": {\n            \"type\": \"count\"\n        },\n    }\n}\n```\n\nAvailable truncations:\n\n- year\n- quarter\n- month\n- week\n- day\n- hour\n- minute\n- second\n\n\nFor mo details about truncations read [Django Docs](https://docs.djangoproject.com/en/3.1/ref/models/database-functions/#trunc)\n",
    "bugtrack_url": null,
    "license": "MIT License  Copyright (c) 2020 Nikita Balobanov  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. ",
    "summary": "Get DB aggregations using Django ORM",
    "version": "2.1.0",
    "project_urls": {
        "Homepage": "https://github.com/kit-oz/drf-aggregation"
    },
    "split_keywords": [
        "django",
        " aggregation",
        " django-rest-framework",
        " django-orm"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "9c410e9074222a0ff21c504348e99c4056b80fa147e2519260087c60809178b4",
                "md5": "6d269deffeca7e74e3c76bf6b17e238d",
                "sha256": "2ccd69f94d0acaa7bcb222eb24cc220423f9327a3adc47a9a003728c023a8cd8"
            },
            "downloads": -1,
            "filename": "drf_aggregation-2.1.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "6d269deffeca7e74e3c76bf6b17e238d",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.8",
            "size": 14300,
            "upload_time": "2024-10-15T20:01:36",
            "upload_time_iso_8601": "2024-10-15T20:01:36.743201Z",
            "url": "https://files.pythonhosted.org/packages/9c/41/0e9074222a0ff21c504348e99c4056b80fa147e2519260087c60809178b4/drf_aggregation-2.1.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "ea184b9ade971084b578756b7b354549aa16eeede04d0e612fea0ab8e8ffd938",
                "md5": "f74d5925cb53db7262196a663a9eaa46",
                "sha256": "134f9a04c9d5e86193558e69056ca5f070c7e0e0ec76be573b583f1eb381cf49"
            },
            "downloads": -1,
            "filename": "drf_aggregation-2.1.0.tar.gz",
            "has_sig": false,
            "md5_digest": "f74d5925cb53db7262196a663a9eaa46",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.8",
            "size": 18805,
            "upload_time": "2024-10-15T20:01:37",
            "upload_time_iso_8601": "2024-10-15T20:01:37.948797Z",
            "url": "https://files.pythonhosted.org/packages/ea/18/4b9ade971084b578756b7b354549aa16eeede04d0e612fea0ab8e8ffd938/drf_aggregation-2.1.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2024-10-15 20:01:37",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "kit-oz",
    "github_project": "drf-aggregation",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "requirements": [],
    "lcname": "drf-aggregation"
}
        
Elapsed time: 4.31696s