Name | pymnesia JSON |
Version |
1.0.1b1
JSON |
| download |
home_page | |
Summary | A (real) in memory database and ORM |
upload_time | 2023-12-19 15:51:59 |
maintainer | |
docs_url | None |
author | Harold Cohen |
requires_python | >=3.10.6,<4.0.0 |
license | MIT |
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 ☑ | yes ☑ |
| float | yes ☑ | yes ☑ |
| str | yes ☑ | yes ☑ |
| bool | yes ☑ | yes ☑ |
| date | not officially | not officially |
| datetime | not officially | not officially |
| tuple | yes ☑ | not officially |
| list | no ☒ | no ☒ |
| dict | not officially | no ☒ |
| one to one relation | yes ☑ | yes ☑ |
| one to many relation | yes ☑ | yes ☑ |
| many to one relation | no ☒ | no ☒ |
| many to many relation | no ☒ | no ☒ |
<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 ☑ | yes ☑ |\n| float | yes ☑ | yes ☑ |\n| str | yes ☑ | yes ☑ |\n| bool | yes ☑ | yes ☑ |\n| date | not officially | not officially |\n| datetime | not officially | not officially |\n| tuple | yes ☑ | not officially |\n| list | no ☒ | no ☒ |\n| dict | not officially | no ☒ |\n| one to one relation | yes ☑ | yes ☑ |\n| one to many relation | yes ☑ | yes ☑ |\n| many to one relation | no ☒ | no ☒ |\n| many to many relation | no ☒ | no ☒ |\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"
}