# Fractal Specifications
> Fractal Specifications is an implementation of the specification pattern for building SOLID logic for your Python applications.
[![PyPI Version][pypi-image]][pypi-url]
[![Build Status][build-image]][build-url]
[![Code Coverage][coverage-image]][coverage-url]
[![Code Quality][quality-image]][quality-url]
<!-- Badges -->
[pypi-image]: https://img.shields.io/pypi/v/fractal-specifications
[pypi-url]: https://pypi.org/project/fractal-specifications/
[build-image]: https://github.com/douwevandermeij/fractal-specifications/actions/workflows/build.yml/badge.svg
[build-url]: https://github.com/douwevandermeij/fractal-specifications/actions/workflows/build.yml
[coverage-image]: https://codecov.io/gh/douwevandermeij/fractal-specifications/branch/main/graph/badge.svg?token=BOC1ZUJISV
[coverage-url]: https://codecov.io/gh/douwevandermeij/fractal-specifications
[quality-image]: https://api.codeclimate.com/v1/badges/455ddff201b43f9b1025/maintainability
[quality-url]: https://codeclimate.com/github/douwevandermeij/fractal-specifications
## Installation
```sh
pip install fractal-specifications
```
## Background
This project comes with an [article on Medium](https://douwevandermeij.medium.com/specification-pattern-in-python-ff2bd0b603f6),
which sets out what the specification pattern is, what the benefits are and how it can be used.
## Development
Setup the development environment by running:
```sh
make deps
pre-commit install
```
Happy coding.
Occasionally you can run:
```sh
make lint
```
This is not explicitly necessary because the git hook does the same thing.
**Do not disable the git hooks upon commit!**
## Usage
Specifications can be used to encapsulate business rules.
An example specification is `EqualsSpecification("maximum_speed", 25)`.
A specification implements the `is_satisfied_by(obj)` function that returns `True` or `False`,
depending on the state of the `obj` that is passed into the function as parameter.
In our example, the `obj` needs to provide the attribute `maximum_speed`.
### Full code example
This example includes a repository to show an application of specifications.
```python
from abc import ABC, abstractmethod
from dataclasses import dataclass
from typing import List
from fractal_specifications.generic.operators import EqualsSpecification
from fractal_specifications.generic.specification import Specification
@dataclass
class Road:
maximum_speed: int
@staticmethod
def slow_roads_specification() -> Specification:
return EqualsSpecification("maximum_speed", 25)
class RoadRepository(ABC):
@abstractmethod
def get_all(self, specification: Specification) -> List[Road]:
...
def slow_roads(self) -> List[Road]:
return self.get_all(Road.slow_roads_specification())
class PythonListRoadRepository(RoadRepository):
def __init__(self, roads: List[Road]):
self.roads = roads
def get_all(self, specification: Specification) -> List[Road]:
return [
road for road in self.roads
if specification.is_satisfied_by(road)
]
if __name__ == "__main__":
road_repository = PythonListRoadRepository([
Road(maximum_speed=25),
Road(maximum_speed=50),
Road(maximum_speed=80),
Road(maximum_speed=100),
])
print(road_repository.slow_roads())
```
## Serialization / deserialization
Specifications can be exported as dictionary and loaded as such via `spec.to_dict()` and `Specification.from_dict(d)` respectively.
Specifications can also be exported to JSON via `spec.dumps()`. This essentially is a `json.dumps()` call around `spec.to_dict()`.
JSON specification strings can be loaded directly as Specification object via `Specification.loads(s)`.
Via this mechanism, specifications can be used outside the application runtime environment. For example, in a database or sent via API.
### Domain Specific Language (DSL)
Apart from basic JSON serialization, Fractal Specifications also comes with a DSL.
Example specifications DSL strings:
- `field_name == 10`
- This is a simple comparison expression with a numerical value.
- `obj.id == 10`
- This is a comparison expression on an object attribute with a numerical value.
- `name != 'John'`
- This is another comparison expression with a string value.
- `age >= 18 && is_student == True`
- This is a logical AND operation between two comparison expressions and a boolean value.
- `roles contains "admin" || roles contains "editor"`
- This is a logical OR operation between two values of a list field.
- `!(active == True)`
- This is a negation of an expression.
- `name in ['John', 'Jane']`
- This is an in_expression that checks if a field value is present in a list of values.
- `email matches \"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}\"`
- This is a regex match_expression that checks if a field value matches a given pattern.
- `items contains "element"`
- This is a contains_expression that checks if a list field contains a given value
- Contains can sometimes also be used with substrings, e.g, when using `is_satisfied_by`.
- `salary is None`
- This is an is_none_expression that checks if a field value is None.
- `#`
- This is an empty_expression that represents an empty expression.
Specifications can be loaded from a DSL string with `spec = Specification.load_dsl(dsl_string)`.\
Specifications can be serialized to a DSL string using `spec.dump_dsl()`.
Example:
```python
from dataclasses import dataclass
from fractal_specifications.generic.specification import Specification
@dataclass
class Demo:
field: str
spec = Specification.load_dsl("field matches 'f.{20}s'")
spec.is_satisfied_by(Demo("fractal_specifications")) # True
```
## Contrib
This library also comes with some additional helpers to integrate the specifications easier with existing backends,
such as the Django ORM.
### Django
Specifications can easily be converted to (basic) Django ORM filters with `DjangoOrmSpecificationBuilder`.\
Using this contrib package requires `django` to be installed.
Query support:
* [x] Direct model fields `field=value`
* [x] Indirect model fields `field__sub_field=value`
* Example: `EqualsSpecification("field__sub_field", "abc")`
* Implies recursive subfields `field__sub_field__sub_sub_field=value`
* This holds for all operators below as well as for Django specific operators
* Example: `EqualsSpecification("field__sub_field__startswith", "ab")`
* When using parse, make sure to use the `_lookup_separator="__"`:
* Default, the resulting parsed specification will contain `"."` as separator
* `Specification.parse(field__sub_field="abc", _lookup_separator="__")`
* Will result in: `EqualsSpecification("field__sub_field", "abc")` instead of `EqualsSpecification("field.sub_field", "abc")`
* [x] Equals `field=value` or `__exact`
* [x] Less than `__lt`
* [x] Less than equal `__lte`
* [x] Greater than `__gt`
* [x] Greater than equal `__gte`
* [x] In `__in`
* [x] And `Q((field_a=value_a) & (field_b=value_b))`
* [x] Or `Q((field_a=value_a) | (field_b=value_b))`
* [x] Partial regex `__regex=r".* value .*"`
* [ ] Full regex `__regex`
* [ ] Contains regex `__contains`
* [x] Is null `__isnull`
```python
from abc import ABC, abstractmethod
from django.db import models
from typing import List
from fractal_specifications.contrib.django.specifications import DjangoOrmSpecificationBuilder
from fractal_specifications.generic.operators import EqualsSpecification
from fractal_specifications.generic.specification import Specification
class Road(models.Model):
maximum_speed = models.IntegerField()
@staticmethod
def slow_roads_specification() -> Specification:
return EqualsSpecification("maximum_speed", 25)
class RoadRepository(ABC):
@abstractmethod
def get_all(self, specification: Specification) -> List[Road]:
...
def slow_roads(self) -> List[Road]:
return self.get_all(Road.slow_roads_specification())
class DjangoRoadRepository(RoadRepository):
def get_all(self, specification: Specification) -> List[Road]:
if q := DjangoOrmSpecificationBuilder.build(specification):
return Road.objects.filter(q)
return Road.objects.all()
if __name__ == "__main__":
road_repository = DjangoRoadRepository()
print(road_repository.slow_roads())
```
You could of course also skip the repository in between and do the filtering directly:
```python
from fractal_specifications.contrib.django.specifications import DjangoOrmSpecificationBuilder
q = DjangoOrmSpecificationBuilder.build(Road.slow_roads_specification())
Road.objects.filter(q)
```
### SQLAlchemy
Query support:
* [x] Direct model fields `{field: value}`
* [x] And `{field: value, field2: value2}`
* [x] Or `[{field: value}, {field2: value2}]`
```python
from fractal_specifications.contrib.sqlalchemy.specifications import SqlAlchemyOrmSpecificationBuilder
q = SqlAlchemyOrmSpecificationBuilder.build(specification)
```
### Elasticsearch
Using this contrib package requires `elasticsearch` to be installed.
Query support:
* [x] Exact term match (Equals) `{"match": {"%s.keyword" % field: value}}`
* [x] String searches (In) `{"query_string": {"default_field": field, "query": value}}`
* [x] And `{"bool": {"must": [...]}}`
* [x] Or `{"bool": {"should": [...]}}`
* [x] Less than `{"bool": {"filter": [{"range": {field: {"lt": value}}}]}}`
* [x] Less than equal `{"bool": {"filter": [{"range": {field: {"lte": value}}}]}}`
* [x] Greater than `{"bool": {"filter": [{"range": {field: {"gt": value}}}]}}`
* [x] Greater than equal `{"bool": {"filter": [{"range": {field: {"gte": value}}}]}}`
```python
from elasticsearch import Elasticsearch
from fractal_specifications.contrib.elasticsearch.specifications import ElasticSpecificationBuilder
q = ElasticSpecificationBuilder.build(specification)
Elasticsearch(...).search(body={"query": q})
```
### Google Firestore
Query support:
* [x] Equals `(field, "==", value)`
* [x] And `[(field, "==", value), (field2, "==", value2)]`
* [x] Contains `(field, "array-contains", value)`
* [x] In `(field, "in", value)`
* [x] Less than `(field, "<", value)`
* [x] Less than equal `(field, "<=", value)`
* [x] Greater than `(field, ">", value)`
* [x] Greater than equal `(field, ">=", value)`
```python
from fractal_specifications.contrib.google_firestore.specifications import FirestoreSpecificationBuilder
q = FirestoreSpecificationBuilder.build(specification)
```
### Mongo
Query support:
* [x] Equals `{field: {"$eq": value}}`
* [x] And `{"$and": [{field: {"$eq": value}}, {field2: {"$eq": value2}}]}`
* [x] Or `{"or": [{field: {"$eq": value}}, {field2: {"$eq": value2}}]}`
* [x] In `{field: {"$in": value}}`
* [x] Less than `{field: {"$lt": value}}`
* [x] Less than equal `{field: {"$lte": value}}`
* [x] Greater than `{field: {"$gt": value}}`
* [x] Greater than equal `{field: {"$gte": value}}`
* [x] Regex string match `{field: {"$regex": ".*%s.*" % value}}`
```python
from fractal_specifications.contrib.mongo.specifications import MongoSpecificationBuilder
q = MongoSpecificationBuilder.build(specification)
```
### Pandas
Pandas support comes in two different flavours.
You can use _columns_ or _indexes_ to filter on.
#### Filtering on columns
Query support:
* [x] Equals `df[field] == value`
* [x] And `(df[field] == value) & (df[field2] == value2)`
* [x] Or `(df[field] == value) | (df[field2] == value2)`
* [x] In `df[field].isin[value]`
* [x] Less than `df[field] < value`
* [x] Less than equal `df[field] <= value`
* [x] Greater than `df[field] > value`
* [x] Greater than equal `df[field] >= value`
* [x] Is null `df[field].isna()`
```python
import pandas as pd
from fractal_specifications.contrib.pandas.specifications import PandasSpecificationBuilder
from fractal_specifications.generic.operators import EqualsSpecification, IsNoneSpecification
df = pd.DataFrame(
{
"id": [1, 2, 3, 4],
"name": ["aa", "bb", "cc", "dd"],
"field": ["x", "y", "z", None],
}
)
print(df)
# id name field
# 0 1 aa x
# 1 2 bb y
# 2 3 cc z
# 3 4 dd None
specification = EqualsSpecification("id", 4)
f1 = PandasSpecificationBuilder.build(specification)
print(f1(df))
# id name field
# 3 4 dd None
specification = IsNoneSpecification("field")
f2 = PandasSpecificationBuilder.build(specification)
print(f2(df))
# id name field
# 3 4 dd None
print(df.pipe(f1).pipe(f2))
# id name field
# 3 4 dd None
specification = EqualsSpecification("id", 4) & IsNoneSpecification("field")
f3 = PandasSpecificationBuilder.build(specification)
print(f3(df))
# id name field
# 3 4 dd None
```
#### Filtering on indexes
Query support:
* [x] Equals `df.index.get_level_values(field) == value`
* [x] And `(df.index.get_level_values(field) == value) & (df.index.get_level_values(field2) == value2)`
* [x] Or `(df.index.get_level_values(field) == value) | (df.index.get_level_values(field2) == value2)`
* [x] In `df.index.get_level_values(field).isin[value]`
* [x] Less than `df.index.get_level_values(field) < value`
* [x] Less than equal `df.index.get_level_values(field) <= value`
* [x] Greater than `df.index.get_level_values(field) > value`
* [x] Greater than equal `df.index.get_level_values(field) >= value`
* [x] Is null `df.index.get_level_values(field).isna()`
```python
import pandas as pd
from fractal_specifications.contrib.pandas.specifications import PandasIndexSpecificationBuilder
from fractal_specifications.generic.operators import EqualsSpecification, GreaterThanSpecification
df = pd.DataFrame({"month": [1, 4, 7, 10],
"year": [2012, 2014, 2013, 2014],
"sale": [55, 40, 84, 31]})
df = df.set_index("month")
print(df)
# year sale
# month
# 1 2012 55
# 4 2014 40
# 7 2013 84
# 10 2014 31
specification = EqualsSpecification("month", 4)
f1 = PandasIndexSpecificationBuilder.build(specification)
print(f1(df))
# year sale
# month
# 4 2014 40
df = df.reset_index()
df = df.set_index("year")
specification = GreaterThanSpecification("year", 2013)
f2 = PandasIndexSpecificationBuilder.build(specification)
print(f2(df))
# month sale
# year
# 2014 4 40
# 2014 10 31
df = df.reset_index()
df = df.set_index(["month", "year"])
print(df.pipe(f1).pipe(f2))
# sale
# month year
# 4 2014 40
specification = EqualsSpecification("month", 4) & GreaterThanSpecification("year", 2013)
f3 = PandasIndexSpecificationBuilder.build(specification)
print(f3(df))
# sale
# month year
# 4 2014 40
```
Raw data
{
"_id": null,
"home_page": "https://github.com/douwevandermeij/fractal-specifications",
"name": "fractal-specifications",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.8",
"maintainer_email": null,
"keywords": null,
"author": "Douwe van der Meij",
"author_email": "douwe@karibu-online.nl",
"download_url": "https://files.pythonhosted.org/packages/b1/6e/140d2a26b67bbea3d376ceaef740beb7df2f33a8765fb64a5979637cb95f/fractal_specifications-3.2.0.tar.gz",
"platform": null,
"description": "# Fractal Specifications\n\n> Fractal Specifications is an implementation of the specification pattern for building SOLID logic for your Python applications.\n\n[![PyPI Version][pypi-image]][pypi-url]\n[![Build Status][build-image]][build-url]\n[![Code Coverage][coverage-image]][coverage-url]\n[![Code Quality][quality-image]][quality-url]\n\n<!-- Badges -->\n\n[pypi-image]: https://img.shields.io/pypi/v/fractal-specifications\n[pypi-url]: https://pypi.org/project/fractal-specifications/\n[build-image]: https://github.com/douwevandermeij/fractal-specifications/actions/workflows/build.yml/badge.svg\n[build-url]: https://github.com/douwevandermeij/fractal-specifications/actions/workflows/build.yml\n[coverage-image]: https://codecov.io/gh/douwevandermeij/fractal-specifications/branch/main/graph/badge.svg?token=BOC1ZUJISV\n[coverage-url]: https://codecov.io/gh/douwevandermeij/fractal-specifications\n[quality-image]: https://api.codeclimate.com/v1/badges/455ddff201b43f9b1025/maintainability\n[quality-url]: https://codeclimate.com/github/douwevandermeij/fractal-specifications\n\n## Installation\n\n```sh\npip install fractal-specifications\n```\n\n\n## Background\n\nThis project comes with an [article on Medium](https://douwevandermeij.medium.com/specification-pattern-in-python-ff2bd0b603f6),\nwhich sets out what the specification pattern is, what the benefits are and how it can be used.\n\n\n## Development\n\nSetup the development environment by running:\n\n```sh\nmake deps\npre-commit install\n```\n\nHappy coding.\n\nOccasionally you can run:\n\n```sh\nmake lint\n```\n\nThis is not explicitly necessary because the git hook does the same thing.\n\n**Do not disable the git hooks upon commit!**\n\n## Usage\n\nSpecifications can be used to encapsulate business rules.\nAn example specification is `EqualsSpecification(\"maximum_speed\", 25)`.\n\nA specification implements the `is_satisfied_by(obj)` function that returns `True` or `False`,\ndepending on the state of the `obj` that is passed into the function as parameter.\nIn our example, the `obj` needs to provide the attribute `maximum_speed`.\n\n### Full code example\n\nThis example includes a repository to show an application of specifications.\n\n```python\nfrom abc import ABC, abstractmethod\nfrom dataclasses import dataclass\nfrom typing import List\n\nfrom fractal_specifications.generic.operators import EqualsSpecification\nfrom fractal_specifications.generic.specification import Specification\n\n\n@dataclass\nclass Road:\n maximum_speed: int\n\n @staticmethod\n def slow_roads_specification() -> Specification:\n return EqualsSpecification(\"maximum_speed\", 25)\n\n\nclass RoadRepository(ABC):\n @abstractmethod\n def get_all(self, specification: Specification) -> List[Road]:\n ...\n\n def slow_roads(self) -> List[Road]:\n return self.get_all(Road.slow_roads_specification())\n\n\nclass PythonListRoadRepository(RoadRepository):\n def __init__(self, roads: List[Road]):\n self.roads = roads\n\n def get_all(self, specification: Specification) -> List[Road]:\n return [\n road for road in self.roads\n if specification.is_satisfied_by(road)\n ]\n\n\nif __name__ == \"__main__\":\n road_repository = PythonListRoadRepository([\n Road(maximum_speed=25),\n Road(maximum_speed=50),\n Road(maximum_speed=80),\n Road(maximum_speed=100),\n ])\n\n print(road_repository.slow_roads())\n```\n\n## Serialization / deserialization\n\nSpecifications can be exported as dictionary and loaded as such via `spec.to_dict()` and `Specification.from_dict(d)` respectively.\n\nSpecifications can also be exported to JSON via `spec.dumps()`. This essentially is a `json.dumps()` call around `spec.to_dict()`.\n\nJSON specification strings can be loaded directly as Specification object via `Specification.loads(s)`.\n\nVia this mechanism, specifications can be used outside the application runtime environment. For example, in a database or sent via API.\n\n### Domain Specific Language (DSL)\n\nApart from basic JSON serialization, Fractal Specifications also comes with a DSL.\n\nExample specifications DSL strings:\n\n- `field_name == 10`\n - This is a simple comparison expression with a numerical value.\n- `obj.id == 10`\n - This is a comparison expression on an object attribute with a numerical value.\n- `name != 'John'`\n - This is another comparison expression with a string value.\n- `age >= 18 && is_student == True`\n - This is a logical AND operation between two comparison expressions and a boolean value.\n- `roles contains \"admin\" || roles contains \"editor\"`\n - This is a logical OR operation between two values of a list field.\n- `!(active == True)`\n - This is a negation of an expression.\n- `name in ['John', 'Jane']`\n - This is an in_expression that checks if a field value is present in a list of values.\n- `email matches \\\"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}\\\"`\n - This is a regex match_expression that checks if a field value matches a given pattern.\n- `items contains \"element\"`\n - This is a contains_expression that checks if a list field contains a given value\n - Contains can sometimes also be used with substrings, e.g, when using `is_satisfied_by`.\n- `salary is None`\n - This is an is_none_expression that checks if a field value is None.\n- `#`\n - This is an empty_expression that represents an empty expression.\n\nSpecifications can be loaded from a DSL string with `spec = Specification.load_dsl(dsl_string)`.\\\nSpecifications can be serialized to a DSL string using `spec.dump_dsl()`.\n\nExample:\n```python\nfrom dataclasses import dataclass\n\nfrom fractal_specifications.generic.specification import Specification\n\n\n@dataclass\nclass Demo:\n field: str\n\n\nspec = Specification.load_dsl(\"field matches 'f.{20}s'\")\nspec.is_satisfied_by(Demo(\"fractal_specifications\")) # True\n```\n\n## Contrib\n\nThis library also comes with some additional helpers to integrate the specifications easier with existing backends,\nsuch as the Django ORM.\n\n### Django\n\nSpecifications can easily be converted to (basic) Django ORM filters with `DjangoOrmSpecificationBuilder`.\\\nUsing this contrib package requires `django` to be installed.\n\nQuery support:\n* [x] Direct model fields `field=value`\n* [x] Indirect model fields `field__sub_field=value`\n * Example: `EqualsSpecification(\"field__sub_field\", \"abc\")`\n * Implies recursive subfields `field__sub_field__sub_sub_field=value`\n * This holds for all operators below as well as for Django specific operators\n * Example: `EqualsSpecification(\"field__sub_field__startswith\", \"ab\")`\n * When using parse, make sure to use the `_lookup_separator=\"__\"`:\n * Default, the resulting parsed specification will contain `\".\"` as separator\n * `Specification.parse(field__sub_field=\"abc\", _lookup_separator=\"__\")`\n * Will result in: `EqualsSpecification(\"field__sub_field\", \"abc\")` instead of `EqualsSpecification(\"field.sub_field\", \"abc\")`\n* [x] Equals `field=value` or `__exact`\n* [x] Less than `__lt`\n* [x] Less than equal `__lte`\n* [x] Greater than `__gt`\n* [x] Greater than equal `__gte`\n* [x] In `__in`\n* [x] And `Q((field_a=value_a) & (field_b=value_b))`\n* [x] Or `Q((field_a=value_a) | (field_b=value_b))`\n* [x] Partial regex `__regex=r\".* value .*\"`\n* [ ] Full regex `__regex`\n* [ ] Contains regex `__contains`\n* [x] Is null `__isnull`\n\n```python\nfrom abc import ABC, abstractmethod\nfrom django.db import models\nfrom typing import List\n\nfrom fractal_specifications.contrib.django.specifications import DjangoOrmSpecificationBuilder\nfrom fractal_specifications.generic.operators import EqualsSpecification\nfrom fractal_specifications.generic.specification import Specification\n\n\nclass Road(models.Model):\n maximum_speed = models.IntegerField()\n\n @staticmethod\n def slow_roads_specification() -> Specification:\n return EqualsSpecification(\"maximum_speed\", 25)\n\n\nclass RoadRepository(ABC):\n @abstractmethod\n def get_all(self, specification: Specification) -> List[Road]:\n ...\n\n def slow_roads(self) -> List[Road]:\n return self.get_all(Road.slow_roads_specification())\n\n\nclass DjangoRoadRepository(RoadRepository):\n def get_all(self, specification: Specification) -> List[Road]:\n if q := DjangoOrmSpecificationBuilder.build(specification):\n return Road.objects.filter(q)\n return Road.objects.all()\n\n\nif __name__ == \"__main__\":\n road_repository = DjangoRoadRepository()\n\n print(road_repository.slow_roads())\n```\n\nYou could of course also skip the repository in between and do the filtering directly:\n\n```python\nfrom fractal_specifications.contrib.django.specifications import DjangoOrmSpecificationBuilder\n\nq = DjangoOrmSpecificationBuilder.build(Road.slow_roads_specification())\nRoad.objects.filter(q)\n```\n\n### SQLAlchemy\n\nQuery support:\n* [x] Direct model fields `{field: value}`\n* [x] And `{field: value, field2: value2}`\n* [x] Or `[{field: value}, {field2: value2}]`\n\n```python\nfrom fractal_specifications.contrib.sqlalchemy.specifications import SqlAlchemyOrmSpecificationBuilder\n\nq = SqlAlchemyOrmSpecificationBuilder.build(specification)\n```\n\n### Elasticsearch\n\nUsing this contrib package requires `elasticsearch` to be installed.\n\nQuery support:\n* [x] Exact term match (Equals) `{\"match\": {\"%s.keyword\" % field: value}}`\n* [x] String searches (In) `{\"query_string\": {\"default_field\": field, \"query\": value}}`\n* [x] And `{\"bool\": {\"must\": [...]}}`\n* [x] Or `{\"bool\": {\"should\": [...]}}`\n* [x] Less than `{\"bool\": {\"filter\": [{\"range\": {field: {\"lt\": value}}}]}}`\n* [x] Less than equal `{\"bool\": {\"filter\": [{\"range\": {field: {\"lte\": value}}}]}}`\n* [x] Greater than `{\"bool\": {\"filter\": [{\"range\": {field: {\"gt\": value}}}]}}`\n* [x] Greater than equal `{\"bool\": {\"filter\": [{\"range\": {field: {\"gte\": value}}}]}}`\n\n```python\nfrom elasticsearch import Elasticsearch\nfrom fractal_specifications.contrib.elasticsearch.specifications import ElasticSpecificationBuilder\n\nq = ElasticSpecificationBuilder.build(specification)\nElasticsearch(...).search(body={\"query\": q})\n```\n\n### Google Firestore\n\nQuery support:\n* [x] Equals `(field, \"==\", value)`\n* [x] And `[(field, \"==\", value), (field2, \"==\", value2)]`\n* [x] Contains `(field, \"array-contains\", value)`\n* [x] In `(field, \"in\", value)`\n* [x] Less than `(field, \"<\", value)`\n* [x] Less than equal `(field, \"<=\", value)`\n* [x] Greater than `(field, \">\", value)`\n* [x] Greater than equal `(field, \">=\", value)`\n\n```python\nfrom fractal_specifications.contrib.google_firestore.specifications import FirestoreSpecificationBuilder\n\nq = FirestoreSpecificationBuilder.build(specification)\n```\n\n### Mongo\n\nQuery support:\n* [x] Equals `{field: {\"$eq\": value}}`\n* [x] And `{\"$and\": [{field: {\"$eq\": value}}, {field2: {\"$eq\": value2}}]}`\n* [x] Or `{\"or\": [{field: {\"$eq\": value}}, {field2: {\"$eq\": value2}}]}`\n* [x] In `{field: {\"$in\": value}}`\n* [x] Less than `{field: {\"$lt\": value}}`\n* [x] Less than equal `{field: {\"$lte\": value}}`\n* [x] Greater than `{field: {\"$gt\": value}}`\n* [x] Greater than equal `{field: {\"$gte\": value}}`\n* [x] Regex string match `{field: {\"$regex\": \".*%s.*\" % value}}`\n\n```python\nfrom fractal_specifications.contrib.mongo.specifications import MongoSpecificationBuilder\n\nq = MongoSpecificationBuilder.build(specification)\n```\n\n### Pandas\n\nPandas support comes in two different flavours.\nYou can use _columns_ or _indexes_ to filter on.\n\n#### Filtering on columns\n\nQuery support:\n* [x] Equals `df[field] == value`\n* [x] And `(df[field] == value) & (df[field2] == value2)`\n* [x] Or `(df[field] == value) | (df[field2] == value2)`\n* [x] In `df[field].isin[value]`\n* [x] Less than `df[field] < value`\n* [x] Less than equal `df[field] <= value`\n* [x] Greater than `df[field] > value`\n* [x] Greater than equal `df[field] >= value`\n* [x] Is null `df[field].isna()`\n\n```python\nimport pandas as pd\n\nfrom fractal_specifications.contrib.pandas.specifications import PandasSpecificationBuilder\nfrom fractal_specifications.generic.operators import EqualsSpecification, IsNoneSpecification\n\n\ndf = pd.DataFrame(\n {\n \"id\": [1, 2, 3, 4],\n \"name\": [\"aa\", \"bb\", \"cc\", \"dd\"],\n \"field\": [\"x\", \"y\", \"z\", None],\n }\n)\n\nprint(df)\n# id name field\n# 0 1 aa x\n# 1 2 bb y\n# 2 3 cc z\n# 3 4 dd None\n\n\nspecification = EqualsSpecification(\"id\", 4)\nf1 = PandasSpecificationBuilder.build(specification)\n\nprint(f1(df))\n# id name field\n# 3 4 dd None\n\n\nspecification = IsNoneSpecification(\"field\")\nf2 = PandasSpecificationBuilder.build(specification)\n\nprint(f2(df))\n# id name field\n# 3 4 dd None\n\n\nprint(df.pipe(f1).pipe(f2))\n# id name field\n# 3 4 dd None\n\n\nspecification = EqualsSpecification(\"id\", 4) & IsNoneSpecification(\"field\")\nf3 = PandasSpecificationBuilder.build(specification)\n\nprint(f3(df))\n# id name field\n# 3 4 dd None\n```\n\n#### Filtering on indexes\n\nQuery support:\n* [x] Equals `df.index.get_level_values(field) == value`\n* [x] And `(df.index.get_level_values(field) == value) & (df.index.get_level_values(field2) == value2)`\n* [x] Or `(df.index.get_level_values(field) == value) | (df.index.get_level_values(field2) == value2)`\n* [x] In `df.index.get_level_values(field).isin[value]`\n* [x] Less than `df.index.get_level_values(field) < value`\n* [x] Less than equal `df.index.get_level_values(field) <= value`\n* [x] Greater than `df.index.get_level_values(field) > value`\n* [x] Greater than equal `df.index.get_level_values(field) >= value`\n* [x] Is null `df.index.get_level_values(field).isna()`\n\n```python\nimport pandas as pd\n\nfrom fractal_specifications.contrib.pandas.specifications import PandasIndexSpecificationBuilder\nfrom fractal_specifications.generic.operators import EqualsSpecification, GreaterThanSpecification\n\n\ndf = pd.DataFrame({\"month\": [1, 4, 7, 10],\n \"year\": [2012, 2014, 2013, 2014],\n \"sale\": [55, 40, 84, 31]})\ndf = df.set_index(\"month\")\n\nprint(df)\n# year sale\n# month\n# 1 2012 55\n# 4 2014 40\n# 7 2013 84\n# 10 2014 31\n\nspecification = EqualsSpecification(\"month\", 4)\nf1 = PandasIndexSpecificationBuilder.build(specification)\n\nprint(f1(df))\n# year sale\n# month\n# 4 2014 40\n\n\ndf = df.reset_index()\ndf = df.set_index(\"year\")\n\nspecification = GreaterThanSpecification(\"year\", 2013)\nf2 = PandasIndexSpecificationBuilder.build(specification)\n\nprint(f2(df))\n# month sale\n# year\n# 2014 4 40\n# 2014 10 31\n\n\ndf = df.reset_index()\ndf = df.set_index([\"month\", \"year\"])\n\nprint(df.pipe(f1).pipe(f2))\n# sale\n# month year\n# 4 2014 40\n\n\nspecification = EqualsSpecification(\"month\", 4) & GreaterThanSpecification(\"year\", 2013)\nf3 = PandasIndexSpecificationBuilder.build(specification)\n\nprint(f3(df))\n# sale\n# month year\n# 4 2014 40\n```\n",
"bugtrack_url": null,
"license": null,
"summary": "Fractal Specifications is an implementation of the specification pattern for building SOLID logic for your Python applications.",
"version": "3.2.0",
"project_urls": {
"Homepage": "https://github.com/douwevandermeij/fractal-specifications"
},
"split_keywords": [],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "b2082a08c16d721ce20817d7a315ef3bc0f184136fe4c77984c2a718886f3a5f",
"md5": "22d3b7ec18ea7909ec9dad96b5994fca",
"sha256": "3b3628d48339535a568782e65a79e6b1db45e66e565189bcacbffafcebc47e05"
},
"downloads": -1,
"filename": "fractal_specifications-3.2.0-py3-none-any.whl",
"has_sig": false,
"md5_digest": "22d3b7ec18ea7909ec9dad96b5994fca",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.8",
"size": 18060,
"upload_time": "2024-10-25T09:37:36",
"upload_time_iso_8601": "2024-10-25T09:37:36.543512Z",
"url": "https://files.pythonhosted.org/packages/b2/08/2a08c16d721ce20817d7a315ef3bc0f184136fe4c77984c2a718886f3a5f/fractal_specifications-3.2.0-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "b16e140d2a26b67bbea3d376ceaef740beb7df2f33a8765fb64a5979637cb95f",
"md5": "6e1cb3d2eaeeaff4b141a19a06cf1595",
"sha256": "1b3c160569030e3836ae9a39afd9b18f5f14d8fd9d2697dbb6a5be6d40ed9317"
},
"downloads": -1,
"filename": "fractal_specifications-3.2.0.tar.gz",
"has_sig": false,
"md5_digest": "6e1cb3d2eaeeaff4b141a19a06cf1595",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.8",
"size": 24377,
"upload_time": "2024-10-25T09:37:38",
"upload_time_iso_8601": "2024-10-25T09:37:38.503902Z",
"url": "https://files.pythonhosted.org/packages/b1/6e/140d2a26b67bbea3d376ceaef740beb7df2f33a8765fb64a5979637cb95f/fractal_specifications-3.2.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2024-10-25 09:37:38",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "douwevandermeij",
"github_project": "fractal-specifications",
"travis_ci": false,
"coveralls": true,
"github_actions": true,
"tox": true,
"lcname": "fractal-specifications"
}