pymnesia


Namepymnesia JSON
Version 1.0.1b1 PyPI version JSON
download
home_page
SummaryA (real) in memory database and ORM
upload_time2023-12-19 15:51:59
maintainer
docs_urlNone
authorHarold Cohen
requires_python>=3.10.6,<4.0.0
licenseMIT
keywords
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # Pymnesia

Pymnesia provides with a real in memory database and ORM to be used in unit tests and in staging environment when
persistence adapters are not available yet.
This tool is likely to be used within decoupled architectures.

## Overview

The current version is beta, but the project is stable and offers a wide range of features that can already be used in
your projects :

- Declare entities with various field types (For a complete list, please refer to the table below.)
- Save entities in an in memory database
- Commit and rollback
- Query stored entities using a very lightweight api and intuitive syntax

### Fields support

|                       | Supported for declaration | Supported for query |
|-----------------------|---------------------------|---------------------|
| int                   | yes &#x2611;              | yes &#x2611;        |
| float                 | yes &#x2611;              | yes &#x2611;        |
| str                   | yes &#x2611;              | yes &#x2611;        |
| bool                  | yes &#x2611;              | yes &#x2611;        |
| date                  | not officially            | not officially      |
| datetime              | not officially            | not officially      |
| tuple                 | yes &#x2611;              | not officially      |
| list                  | no &#x2612;               | no &#x2612;         |
| dict                  | not officially            | no &#x2612;         |
| one to one relation   | yes &#x2611;              | yes &#x2611;        |
| one to many relation  | yes &#x2611;              | yes &#x2611;        |
| many to one relation  | no &#x2612;               | no &#x2612;         |
| many to many relation | no &#x2612;               | no &#x2612;         |

<i>not officially supported: The feature should work but may not be fully functional (querying for instance) and
stability is not guarantied. </i>

## Basic user documentation

Until a more detailed documentation is released, please refer to the examples below for usage.

Please keep in mind that the features will usually be made available through an api import.
Core classes, functions and such should not be imported and used directly.

### Entities

#### Declaring a simple entity

```python
from uuid import UUID

from pymnesia.api.entities.base import declarative_base


class CarEngine(declarative_base()):
    __tablename__ = "car_engines"

    id: UUID

    horsepower: int
```

#### Declaring an entity with a 'one to one' relation

```python
from uuid import UUID

from pymnesia.api.entities.base import declarative_base


class Car(declarative_base()):
    __tablename__ = "cars"

    id: UUID

    name: str

    engine: CarEngine
```

#### Declaring an entity with a 'one to many' relation

The relation api can be used to specify custom options (for now the reverse name and whether the relation is optional or
not).

```python
from uuid import UUID

from pymnesia.api.entities.base import declarative_base
from pymnesia.api.entities.fields import field, relation


class Car(declarative_base()):
    __tablename__ = "cars"

    id: UUID

    name: str = field(default="Peugeot 3008")

    engine: CarEngine

    drivers: List[Driver] = relation(reverse="a_list_of_drivers")
```

### Command

#### Save and commit

```python
from uuid import uuid4

from pymnesia.api.unit_of_work import uow
from pymnesia.api.command import transaction

unit_of_work = uow()
new_transaction = transaction(unit_of_work=unit_of_work)

v12_engine = CarEngine(id=uuid4(), horsepower=400)
aston_martin = CarModel(id=uuid4(), engine_id=v12_engine.id)

unit_of_work.save_entity(entity=v12_engine)
unit_of_work.save_entity(entity=aston_martin)
new_transaction.commit()
```

Querying the database for car models will return one car model (The output will be 400).

```python
for car in unit_of_work.query().cars().fetch():
    print(car.engine.horsepower)
```

#### Rollback

```python
from uuid import uuid4

from pymnesia.api.unit_of_work import uow
from pymnesia.api.command import transaction

unit_of_work = uow()
new_transaction = transaction(unit_of_work=unit_of_work)

v12_engine = CarEngine(id=uuid4(), horsepower=400)
unit_of_work.save_entity(entity=v12_engine)
new_transaction.rollback()

v8_engine = CarEngine(id=uuid4(), horsepower=300)
unit_of_work.save_entity(entity=v8_engine)
new_transaction.commit()
```

Querying the database for car engines will return the v8 engine alone (The output will be 300).

```python
for engine in unit_of_work.query().car_engines().fetch():
    print(engine.horsepower)
```

### Query

#### Fetch

Fetch allows to retrieve multiple results of a query.
To query an entity model, call query() in the unit of work instance containing your entities.
Then simply call a method using the tablename you declared for said entity.

For instance, if you declare the entity below:

```python
from uuid import UUID

from pymnesia.api.entities.base import declarative_base


class Address(declarative_base()):
    __tablename__ = "addresses"

    id: UUID

    street_name: str
```

You will query addresses as follows:

```python
for car in unit_of_work.query().addresses().fetch():
    # whatever you need to do
    pass
```

#### Fetch one

Fetch allows to retrieve the first result of a query.

Given you have two cars in your in memory database, fetch_one() will return the entity that was saved first.

```python
car = unit_of_work.query().addresses().fetch_one()
```

#### Where Or clauses

One of the great features of Pymnesia is how you can add where or clauses to you queries.

<b><i>Where clause</i></b>

To add a where clause use the where method when querying an entity.

<i> 'Equal' operator </i>

The query below will return every car engine that has a 400 horsepower:

```python
for car in unit_of_work.query().car_engines().where({"horsepower": 400}).fetch():
    # whatever you need to do
    pass
```

<i> 'Not equal' operator </i>

The query below will return every car engine that doesn't have a 400 horsepower:

```python
for car in unit_of_work.query().car_engines().where({"horsepower::not": 400}).fetch():
    # whatever you need to do
    pass
```

<i> 'Greater than' operator </i>

The query below will return every car engine that have horsepower greater than 400:

```python
for car in unit_of_work.query().car_engines().where({"horsepower::gt": 400}).fetch():
    # whatever you need to do
    pass
```

<i> 'Greater than or equal to' operator </i>

The query below will return every car engine that have horsepower greater than or equal to 400:

```python
for car in unit_of_work.query().car_engines().where({"horsepower::gte": 400}).fetch():
    # whatever you need to do
    pass
```

<i> 'Less than' operator </i>

The query below will return every car engine that have horsepower lesser than 400:

```python
for car in unit_of_work.query().car_engines().where({"horsepower::lt": 400}).fetch():
    # whatever you need to do
    pass
```

<i> 'Less than or equal to' operator </i>

The query below will return every car engine that have horsepower lesser than or equal to 400:

```python
for car in unit_of_work.query().car_engines().where({"horsepower::lte": 400}).fetch():
    # whatever you need to do
    pass
```

<i> 'Match' operator </i>

The query below will return every car that have a name matching the provided regex:

```python
for car in unit_of_work.query().cars().where({"name::match": r'^Peugeot .*$'}).fetch():
    # whatever you need to do
    pass
```

<i> 'In' operator </i>

The query below will return every car that have a value included in the provided list:

```python
for car in unit_of_work.query().cars().where({"name::in": ["Aston Martin Valkyrie", "Porsche GT3"]}).fetch():
    # whatever you need to do
    pass
```

<i> Relational queries </i>

Every operator documented above can be used to make relational queries:

```python
for car in unit_of_work.query().cars().where({"engine.horsepower::gt": 400}).fetch():
    # whatever you need to do
    pass
```

<b><i>Or clauses</i></b>

You can add one or more 'or clauses' to a query.
Every condition in a 'or clause' is evaluated as OR AND.

For instance the query below:

```python
query = unit_of_work.query().cars().where({"name": "Peugeot 3008"})
query.or_({"name::match": r'^Peugeot .*$', "engine.horsepower::gt": 100})
```

Is the equivalent of an SQL query:

```sql
SELECT *
FROM cars
         JOIN car_engines ON car_engines.id = cars.engine_id
WHERE cars.name = 'Peugeot 3008'
   OR (cars.name LIKE '^Peugeot .*$' AND car_engines.horsepower > 100)
```

Multiple 'or clauses' remain independent of one another:

```python
query = unit_of_work.query().cars().where({"name": "Peugeot 3008"})
query.or_({"name::match": r'^Peugeot .*$', "engine.horsepower::gt": 100})
query.or_({"engine.horsepower::gte": 200})
```

Is the equivalent of an SQL query:

```sql
SELECT *
FROM cars
         JOIN car_engines ON car_engines.id = cars.engine_id
WHERE cars.name = 'Peugeot 3008'
   OR (cars.name LIKE '^Peugeot .*$' AND car_engines.horsepower > 100)
   OR (car_engines.horsepower >= 200)
```

<b><i>Where clause using composition</i></b>

The entities can be queried using composition rather than declarative conditions.
<b>The example below makes little sense</b>, but this feature can be powerful to make complex queries when operator
functions
are not available to perform the requested
operation.

```python
from typing import Iterable
from functools import partial


def car_name_func(entities_: Iterable, field: str, value: str, *args, **kwargs) -> filter:
    return filter(lambda e: getattr(e, field) == value, entities_)


partial_k2000_func = partial(
    car_name_func,
    field="name",
    value="K2000",
)

partial_gran_torino_func = partial(
    car_name_func,
    field="name",
    value="Gran Torino",
)

query = unit_of_work.query().cars().where_with_composition([
    partial_k2000_func,
    partial_gran_torino_func,
])
```

#### Limit

You can limit the number of result using the limit() method.

The query below will limit the number of results to 5 car engines:

```python
for car in unit_of_work.query().car_engines().limit(5).fetch():
    # whatever you need to do
    pass
```

#### Order by

You can order by your results by specifying a direction and a key.

The query below will order the results on the field 'name' in descending order.

```python
for car in unit_of_work.query().cars().order_by(direction="desc", key="name").fetch():
    # whatever you need to do
    pass
```

            

Raw data

            {
    "_id": null,
    "home_page": "",
    "name": "pymnesia",
    "maintainer": "",
    "docs_url": null,
    "requires_python": ">=3.10.6,<4.0.0",
    "maintainer_email": "",
    "keywords": "",
    "author": "Harold Cohen",
    "author_email": "me@harold-cohen.com",
    "download_url": "https://files.pythonhosted.org/packages/a9/09/7a2cf71205a9f3a6d9bfd46a42b4ebfe9bceca00c9b30a39383ede2d7ecd/pymnesia-1.0.1b1.tar.gz",
    "platform": null,
    "description": "# Pymnesia\n\nPymnesia provides with a real in memory database and ORM to be used in unit tests and in staging environment when\npersistence adapters are not available yet.\nThis tool is likely to be used within decoupled architectures.\n\n## Overview\n\nThe current version is beta, but the project is stable and offers a wide range of features that can already be used in\nyour projects :\n\n- Declare entities with various field types (For a complete list, please refer to the table below.)\n- Save entities in an in memory database\n- Commit and rollback\n- Query stored entities using a very lightweight api and intuitive syntax\n\n### Fields support\n\n|                       | Supported for declaration | Supported for query |\n|-----------------------|---------------------------|---------------------|\n| int                   | yes &#x2611;              | yes &#x2611;        |\n| float                 | yes &#x2611;              | yes &#x2611;        |\n| str                   | yes &#x2611;              | yes &#x2611;        |\n| bool                  | yes &#x2611;              | yes &#x2611;        |\n| date                  | not officially            | not officially      |\n| datetime              | not officially            | not officially      |\n| tuple                 | yes &#x2611;              | not officially      |\n| list                  | no &#x2612;               | no &#x2612;         |\n| dict                  | not officially            | no &#x2612;         |\n| one to one relation   | yes &#x2611;              | yes &#x2611;        |\n| one to many relation  | yes &#x2611;              | yes &#x2611;        |\n| many to one relation  | no &#x2612;               | no &#x2612;         |\n| many to many relation | no &#x2612;               | no &#x2612;         |\n\n<i>not officially supported: The feature should work but may not be fully functional (querying for instance) and\nstability is not guarantied. </i>\n\n## Basic user documentation\n\nUntil a more detailed documentation is released, please refer to the examples below for usage.\n\nPlease keep in mind that the features will usually be made available through an api import.\nCore classes, functions and such should not be imported and used directly.\n\n### Entities\n\n#### Declaring a simple entity\n\n```python\nfrom uuid import UUID\n\nfrom pymnesia.api.entities.base import declarative_base\n\n\nclass CarEngine(declarative_base()):\n    __tablename__ = \"car_engines\"\n\n    id: UUID\n\n    horsepower: int\n```\n\n#### Declaring an entity with a 'one to one' relation\n\n```python\nfrom uuid import UUID\n\nfrom pymnesia.api.entities.base import declarative_base\n\n\nclass Car(declarative_base()):\n    __tablename__ = \"cars\"\n\n    id: UUID\n\n    name: str\n\n    engine: CarEngine\n```\n\n#### Declaring an entity with a 'one to many' relation\n\nThe relation api can be used to specify custom options (for now the reverse name and whether the relation is optional or\nnot).\n\n```python\nfrom uuid import UUID\n\nfrom pymnesia.api.entities.base import declarative_base\nfrom pymnesia.api.entities.fields import field, relation\n\n\nclass Car(declarative_base()):\n    __tablename__ = \"cars\"\n\n    id: UUID\n\n    name: str = field(default=\"Peugeot 3008\")\n\n    engine: CarEngine\n\n    drivers: List[Driver] = relation(reverse=\"a_list_of_drivers\")\n```\n\n### Command\n\n#### Save and commit\n\n```python\nfrom uuid import uuid4\n\nfrom pymnesia.api.unit_of_work import uow\nfrom pymnesia.api.command import transaction\n\nunit_of_work = uow()\nnew_transaction = transaction(unit_of_work=unit_of_work)\n\nv12_engine = CarEngine(id=uuid4(), horsepower=400)\naston_martin = CarModel(id=uuid4(), engine_id=v12_engine.id)\n\nunit_of_work.save_entity(entity=v12_engine)\nunit_of_work.save_entity(entity=aston_martin)\nnew_transaction.commit()\n```\n\nQuerying the database for car models will return one car model (The output will be 400).\n\n```python\nfor car in unit_of_work.query().cars().fetch():\n    print(car.engine.horsepower)\n```\n\n#### Rollback\n\n```python\nfrom uuid import uuid4\n\nfrom pymnesia.api.unit_of_work import uow\nfrom pymnesia.api.command import transaction\n\nunit_of_work = uow()\nnew_transaction = transaction(unit_of_work=unit_of_work)\n\nv12_engine = CarEngine(id=uuid4(), horsepower=400)\nunit_of_work.save_entity(entity=v12_engine)\nnew_transaction.rollback()\n\nv8_engine = CarEngine(id=uuid4(), horsepower=300)\nunit_of_work.save_entity(entity=v8_engine)\nnew_transaction.commit()\n```\n\nQuerying the database for car engines will return the v8 engine alone (The output will be 300).\n\n```python\nfor engine in unit_of_work.query().car_engines().fetch():\n    print(engine.horsepower)\n```\n\n### Query\n\n#### Fetch\n\nFetch allows to retrieve multiple results of a query.\nTo query an entity model, call query() in the unit of work instance containing your entities.\nThen simply call a method using the tablename you declared for said entity.\n\nFor instance, if you declare the entity below:\n\n```python\nfrom uuid import UUID\n\nfrom pymnesia.api.entities.base import declarative_base\n\n\nclass Address(declarative_base()):\n    __tablename__ = \"addresses\"\n\n    id: UUID\n\n    street_name: str\n```\n\nYou will query addresses as follows:\n\n```python\nfor car in unit_of_work.query().addresses().fetch():\n    # whatever you need to do\n    pass\n```\n\n#### Fetch one\n\nFetch allows to retrieve the first result of a query.\n\nGiven you have two cars in your in memory database, fetch_one() will return the entity that was saved first.\n\n```python\ncar = unit_of_work.query().addresses().fetch_one()\n```\n\n#### Where Or clauses\n\nOne of the great features of Pymnesia is how you can add where or clauses to you queries.\n\n<b><i>Where clause</i></b>\n\nTo add a where clause use the where method when querying an entity.\n\n<i> 'Equal' operator </i>\n\nThe query below will return every car engine that has a 400 horsepower:\n\n```python\nfor car in unit_of_work.query().car_engines().where({\"horsepower\": 400}).fetch():\n    # whatever you need to do\n    pass\n```\n\n<i> 'Not equal' operator </i>\n\nThe query below will return every car engine that doesn't have a 400 horsepower:\n\n```python\nfor car in unit_of_work.query().car_engines().where({\"horsepower::not\": 400}).fetch():\n    # whatever you need to do\n    pass\n```\n\n<i> 'Greater than' operator </i>\n\nThe query below will return every car engine that have horsepower greater than 400:\n\n```python\nfor car in unit_of_work.query().car_engines().where({\"horsepower::gt\": 400}).fetch():\n    # whatever you need to do\n    pass\n```\n\n<i> 'Greater than or equal to' operator </i>\n\nThe query below will return every car engine that have horsepower greater than or equal to 400:\n\n```python\nfor car in unit_of_work.query().car_engines().where({\"horsepower::gte\": 400}).fetch():\n    # whatever you need to do\n    pass\n```\n\n<i> 'Less than' operator </i>\n\nThe query below will return every car engine that have horsepower lesser than 400:\n\n```python\nfor car in unit_of_work.query().car_engines().where({\"horsepower::lt\": 400}).fetch():\n    # whatever you need to do\n    pass\n```\n\n<i> 'Less than or equal to' operator </i>\n\nThe query below will return every car engine that have horsepower lesser than or equal to 400:\n\n```python\nfor car in unit_of_work.query().car_engines().where({\"horsepower::lte\": 400}).fetch():\n    # whatever you need to do\n    pass\n```\n\n<i> 'Match' operator </i>\n\nThe query below will return every car that have a name matching the provided regex:\n\n```python\nfor car in unit_of_work.query().cars().where({\"name::match\": r'^Peugeot .*$'}).fetch():\n    # whatever you need to do\n    pass\n```\n\n<i> 'In' operator </i>\n\nThe query below will return every car that have a value included in the provided list:\n\n```python\nfor car in unit_of_work.query().cars().where({\"name::in\": [\"Aston Martin Valkyrie\", \"Porsche GT3\"]}).fetch():\n    # whatever you need to do\n    pass\n```\n\n<i> Relational queries </i>\n\nEvery operator documented above can be used to make relational queries:\n\n```python\nfor car in unit_of_work.query().cars().where({\"engine.horsepower::gt\": 400}).fetch():\n    # whatever you need to do\n    pass\n```\n\n<b><i>Or clauses</i></b>\n\nYou can add one or more 'or clauses' to a query.\nEvery condition in a 'or clause' is evaluated as OR AND.\n\nFor instance the query below:\n\n```python\nquery = unit_of_work.query().cars().where({\"name\": \"Peugeot 3008\"})\nquery.or_({\"name::match\": r'^Peugeot .*$', \"engine.horsepower::gt\": 100})\n```\n\nIs the equivalent of an SQL query:\n\n```sql\nSELECT *\nFROM cars\n         JOIN car_engines ON car_engines.id = cars.engine_id\nWHERE cars.name = 'Peugeot 3008'\n   OR (cars.name LIKE '^Peugeot .*$' AND car_engines.horsepower > 100)\n```\n\nMultiple 'or clauses' remain independent of one another:\n\n```python\nquery = unit_of_work.query().cars().where({\"name\": \"Peugeot 3008\"})\nquery.or_({\"name::match\": r'^Peugeot .*$', \"engine.horsepower::gt\": 100})\nquery.or_({\"engine.horsepower::gte\": 200})\n```\n\nIs the equivalent of an SQL query:\n\n```sql\nSELECT *\nFROM cars\n         JOIN car_engines ON car_engines.id = cars.engine_id\nWHERE cars.name = 'Peugeot 3008'\n   OR (cars.name LIKE '^Peugeot .*$' AND car_engines.horsepower > 100)\n   OR (car_engines.horsepower >= 200)\n```\n\n<b><i>Where clause using composition</i></b>\n\nThe entities can be queried using composition rather than declarative conditions.\n<b>The example below makes little sense</b>, but this feature can be powerful to make complex queries when operator\nfunctions\nare not available to perform the requested\noperation.\n\n```python\nfrom typing import Iterable\nfrom functools import partial\n\n\ndef car_name_func(entities_: Iterable, field: str, value: str, *args, **kwargs) -> filter:\n    return filter(lambda e: getattr(e, field) == value, entities_)\n\n\npartial_k2000_func = partial(\n    car_name_func,\n    field=\"name\",\n    value=\"K2000\",\n)\n\npartial_gran_torino_func = partial(\n    car_name_func,\n    field=\"name\",\n    value=\"Gran Torino\",\n)\n\nquery = unit_of_work.query().cars().where_with_composition([\n    partial_k2000_func,\n    partial_gran_torino_func,\n])\n```\n\n#### Limit\n\nYou can limit the number of result using the limit() method.\n\nThe query below will limit the number of results to 5 car engines:\n\n```python\nfor car in unit_of_work.query().car_engines().limit(5).fetch():\n    # whatever you need to do\n    pass\n```\n\n#### Order by\n\nYou can order by your results by specifying a direction and a key.\n\nThe query below will order the results on the field 'name' in descending order.\n\n```python\nfor car in unit_of_work.query().cars().order_by(direction=\"desc\", key=\"name\").fetch():\n    # whatever you need to do\n    pass\n```\n",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "A (real) in memory database and ORM",
    "version": "1.0.1b1",
    "project_urls": null,
    "split_keywords": [],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "767965dbdf2c09c695563a585cf92de0faf317197dd42bafe5e9109dbc34c328",
                "md5": "327d8debdef2221f5f0eba6a18787f66",
                "sha256": "6b12c18e06f075d79382ac4ebc04f2e54cdbe845863a788f97fe13f6e00b8565"
            },
            "downloads": -1,
            "filename": "pymnesia-1.0.1b1-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "327d8debdef2221f5f0eba6a18787f66",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.10.6,<4.0.0",
            "size": 28038,
            "upload_time": "2023-12-19T15:51:56",
            "upload_time_iso_8601": "2023-12-19T15:51:56.565114Z",
            "url": "https://files.pythonhosted.org/packages/76/79/65dbdf2c09c695563a585cf92de0faf317197dd42bafe5e9109dbc34c328/pymnesia-1.0.1b1-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "a9097a2cf71205a9f3a6d9bfd46a42b4ebfe9bceca00c9b30a39383ede2d7ecd",
                "md5": "172a13eab243c5a036153fb4c54f20bc",
                "sha256": "c1e898632f84a004e4a4fcb263c9864866dbfabd45e284ca7bde0c38835faffd"
            },
            "downloads": -1,
            "filename": "pymnesia-1.0.1b1.tar.gz",
            "has_sig": false,
            "md5_digest": "172a13eab243c5a036153fb4c54f20bc",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.10.6,<4.0.0",
            "size": 18547,
            "upload_time": "2023-12-19T15:51:59",
            "upload_time_iso_8601": "2023-12-19T15:51:59.338417Z",
            "url": "https://files.pythonhosted.org/packages/a9/09/7a2cf71205a9f3a6d9bfd46a42b4ebfe9bceca00c9b30a39383ede2d7ecd/pymnesia-1.0.1b1.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2023-12-19 15:51:59",
    "github": false,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "lcname": "pymnesia"
}
        
Elapsed time: 3.24184s