introduce docs -> models section mostly finished
This commit is contained in:
0
docs/fastapi.md
Normal file
0
docs/fastapi.md
Normal file
0
docs/fields.md
Normal file
0
docs/fields.md
Normal file
17
docs/index.md
Normal file
17
docs/index.md
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
# Welcome to MkDocs
|
||||||
|
|
||||||
|
For full documentation visit [mkdocs.org](https://www.mkdocs.org).
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
* `mkdocs new [dir-name]` - Create a new project.
|
||||||
|
* `mkdocs serve` - Start the live-reloading docs server.
|
||||||
|
* `mkdocs build` - Build the documentation site.
|
||||||
|
* `mkdocs -h` - Print help message and exit.
|
||||||
|
|
||||||
|
## Project layout
|
||||||
|
|
||||||
|
mkdocs.yml # The configuration file.
|
||||||
|
docs/
|
||||||
|
index.md # The documentation homepage.
|
||||||
|
... # Other markdown pages, images and other files.
|
||||||
171
docs/models.md
Normal file
171
docs/models.md
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
# Models
|
||||||
|
|
||||||
|
## Defining models
|
||||||
|
By defining an orm Model you get corresponding **Pydantic model** as well as **Sqlalchemy table** for free.
|
||||||
|
They are being managed in the background and you do not have to create them on your own.
|
||||||
|
|
||||||
|
### Model Class
|
||||||
|
To build an ORM model you simply need to inherit a `orm.Model` class.
|
||||||
|
|
||||||
|
```Python hl_lines="10"
|
||||||
|
--8<-- "../docs_src/models/docs001.py"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Defining Fields
|
||||||
|
Next assign one or more of the [Fields][fields] as a class level variables.
|
||||||
|
|
||||||
|
Each table **has to** have a primary key column, which you specify by setting `primary_key=True` on selected field.
|
||||||
|
|
||||||
|
Only one primary key column is allowed.
|
||||||
|
|
||||||
|
```Python hl_lines="14 15 16"
|
||||||
|
--8<-- "../docs_src/models/docs001.py"
|
||||||
|
```
|
||||||
|
|
||||||
|
!!! warning
|
||||||
|
Not assigning `primary_key` column or assigning more than one column per `Model` will raise `ModelDefinitionError`
|
||||||
|
exception.
|
||||||
|
|
||||||
|
By default if you assign primary key to `Integer` field, the `autoincrement` option is set to true.
|
||||||
|
|
||||||
|
You can disable by passing `autoincremant=False`.
|
||||||
|
|
||||||
|
```Python
|
||||||
|
id = orm.Integer(primary_key=True, autoincrement=False)
|
||||||
|
```
|
||||||
|
|
||||||
|
Names of the fields will be used for both the underlying `pydantic` model and `sqlalchemy` table.
|
||||||
|
|
||||||
|
### Dependencies
|
||||||
|
|
||||||
|
Since orm depends on [`databases`][databases] and [`sqlalchemy-core`][sqlalchemy-core] for database connection
|
||||||
|
and table creation you need to assign each `Model` with two special parameters.
|
||||||
|
|
||||||
|
#### Databases
|
||||||
|
One is `Database` instance created with your database url in [sqlalchemy connection string][sqlalchemy connection string] format.
|
||||||
|
|
||||||
|
Created instance needs to be passed to every `Model` with `__database__` parameter.
|
||||||
|
|
||||||
|
```Python hl_lines="1 6 11"
|
||||||
|
--8<-- "../docs_src/models/docs001.py"
|
||||||
|
```
|
||||||
|
|
||||||
|
!!! tip
|
||||||
|
You need to create the `Database` instance **only once** and use it for all models.
|
||||||
|
You can create several ones if you want to use multiple databases.
|
||||||
|
|
||||||
|
#### Sqlalchemy
|
||||||
|
Second dependency is sqlalchemy `MetaData` instance.
|
||||||
|
|
||||||
|
Created instance needs to be passed to every `Model` with `__metadata__` parameter.
|
||||||
|
|
||||||
|
```Python hl_lines="2 7 12"
|
||||||
|
--8<-- "../docs_src/models/docs001.py"
|
||||||
|
```
|
||||||
|
|
||||||
|
!!! tip
|
||||||
|
You need to create the `MetaData` instance **only once** and use it for all models.
|
||||||
|
You can create several ones if you want to use multiple databases.
|
||||||
|
|
||||||
|
### Table Names
|
||||||
|
|
||||||
|
By default table name is created from Model class name as lowercase name plus 's'.
|
||||||
|
|
||||||
|
You can overwrite this parameter by providing `__tablename__` argument.
|
||||||
|
|
||||||
|
```Python hl_lines="11 12 13"
|
||||||
|
--8<-- "../docs_src/models/docs002.py"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Initialization
|
||||||
|
|
||||||
|
There are two ways to create and persist the `Model` instance in the database.
|
||||||
|
|
||||||
|
!!!tip
|
||||||
|
Use `ipython` to try this from the console, since it supports `await`.
|
||||||
|
|
||||||
|
If you plan to modify the instance in the later execution of your program you can initiate your `Model` as a normal class and later await a `save()` call.
|
||||||
|
|
||||||
|
```Python hl_lines="19 20"
|
||||||
|
--8<-- "../docs_src/models/docs007.py"
|
||||||
|
```
|
||||||
|
|
||||||
|
If you want to initiate your `Model` and at the same time save in in the database use a QuerySet's method `create()`.
|
||||||
|
|
||||||
|
Each model has a `QuerySet` initialised as `objects` parameter
|
||||||
|
|
||||||
|
```Python hl_lines="22"
|
||||||
|
--8<-- "../docs_src/models/docs007.py"
|
||||||
|
```
|
||||||
|
|
||||||
|
!!!info
|
||||||
|
To read more about `QuerySets` and available methods visit [queries][queries]
|
||||||
|
|
||||||
|
## Attributes Delegation
|
||||||
|
|
||||||
|
Each call to `Model` fields parameter under the hood is delegated to either the `pydantic` model
|
||||||
|
or other related `Model` in case of relations.
|
||||||
|
|
||||||
|
The fields and relations are not stored on the `Model` itself
|
||||||
|
|
||||||
|
```Python hl_lines="31 32 33 34 35 36 37 38 39 40 41"
|
||||||
|
--8<-- "../docs_src/models/docs006.py"
|
||||||
|
```
|
||||||
|
|
||||||
|
!!! warning
|
||||||
|
In example above model instances are created but not persisted that's why `id` of `department` is None!
|
||||||
|
|
||||||
|
!!!info
|
||||||
|
To read more about `ForeignKeys` and `Model` relations visit [relations][relations]
|
||||||
|
|
||||||
|
## Internals
|
||||||
|
|
||||||
|
Apart from special parameters defined in the `Model` during definition (tablename, metadata etc.) the `Model` provides you with useful internals.
|
||||||
|
|
||||||
|
### Pydantic Model
|
||||||
|
To access auto created pydantic model you can use `Model.__pydantic_model__` parameter
|
||||||
|
|
||||||
|
For example to list model fields you can:
|
||||||
|
|
||||||
|
```Python hl_lines="18"
|
||||||
|
--8<-- "../docs_src/models/docs003.py"
|
||||||
|
```
|
||||||
|
|
||||||
|
!!!tip
|
||||||
|
Note how the primary key `id` field is optional as `Integer` primary key by default has `autoincrement` set to `True`.
|
||||||
|
|
||||||
|
!!!info
|
||||||
|
For more options visit official [pydantic][pydantic] documentation.
|
||||||
|
|
||||||
|
### Sqlalchemy Table
|
||||||
|
To access auto created sqlalchemy table you can use `Model.__table__` parameter
|
||||||
|
|
||||||
|
For example to list table columns you can:
|
||||||
|
|
||||||
|
```Python hl_lines="18"
|
||||||
|
--8<-- "../docs_src/models/docs004.py"
|
||||||
|
```
|
||||||
|
|
||||||
|
!!!tip
|
||||||
|
You can access table primary key name by `Course.__pkname__`
|
||||||
|
|
||||||
|
!!!info
|
||||||
|
For more options visit official [sqlalchemy-metadata][sqlalchemy-metadata] documentation.
|
||||||
|
|
||||||
|
### Fields Definition
|
||||||
|
To access orm `Fields` you can use `Model.__model_fields__` parameter
|
||||||
|
|
||||||
|
For example to list table model fields you can:
|
||||||
|
|
||||||
|
```Python hl_lines="18"
|
||||||
|
--8<-- "../docs_src/models/docs005.py"
|
||||||
|
```
|
||||||
|
|
||||||
|
[fields]: ./fields.md
|
||||||
|
[relations]: ./relations.md
|
||||||
|
[queries]: ./queries.md
|
||||||
|
[pydantic]: https://pydantic-docs.helpmanual.io/
|
||||||
|
[sqlalchemy-core]: https://docs.sqlalchemy.org/en/latest/core/
|
||||||
|
[sqlalchemy-metadata]: https://docs.sqlalchemy.org/en/13/core/metadata.html
|
||||||
|
[databases]: https://github.com/encode/databases
|
||||||
|
[sqlalchemy connection string]: https://docs.sqlalchemy.org/en/13/core/engines.html#database-urls
|
||||||
0
docs/pydantic.md
Normal file
0
docs/pydantic.md
Normal file
0
docs/queries.md
Normal file
0
docs/queries.md
Normal file
0
docs/relations.md
Normal file
0
docs/relations.md
Normal file
16
docs_src/models/docs001.py
Normal file
16
docs_src/models/docs001.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import databases
|
||||||
|
import sqlalchemy
|
||||||
|
|
||||||
|
import orm
|
||||||
|
|
||||||
|
database = databases.Database("sqlite:///db.sqlite")
|
||||||
|
metadata = sqlalchemy.MetaData()
|
||||||
|
|
||||||
|
|
||||||
|
class Course(orm.Model):
|
||||||
|
__database__ = database
|
||||||
|
__metadata__ = metadata
|
||||||
|
|
||||||
|
id = orm.Integer(primary_key=True)
|
||||||
|
name = orm.String(length=100)
|
||||||
|
completed = orm.Boolean(default=False)
|
||||||
19
docs_src/models/docs002.py
Normal file
19
docs_src/models/docs002.py
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import databases
|
||||||
|
import sqlalchemy
|
||||||
|
|
||||||
|
import orm
|
||||||
|
|
||||||
|
database = databases.Database("sqlite:///db.sqlite")
|
||||||
|
metadata = sqlalchemy.MetaData()
|
||||||
|
|
||||||
|
|
||||||
|
class Course(orm.Model):
|
||||||
|
# if you omit this parameter it will be created automatically
|
||||||
|
# as class.__name__.lower()+'s' -> "courses" in this example
|
||||||
|
__tablename__ = "my_courses"
|
||||||
|
__database__ = database
|
||||||
|
__metadata__ = metadata
|
||||||
|
|
||||||
|
id = orm.Integer(primary_key=True)
|
||||||
|
name = orm.String(length=100)
|
||||||
|
completed = orm.Boolean(default=False)
|
||||||
33
docs_src/models/docs003.py
Normal file
33
docs_src/models/docs003.py
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import databases
|
||||||
|
import sqlalchemy
|
||||||
|
|
||||||
|
import orm
|
||||||
|
|
||||||
|
database = databases.Database("sqlite:///db.sqlite")
|
||||||
|
metadata = sqlalchemy.MetaData()
|
||||||
|
|
||||||
|
|
||||||
|
class Course(orm.Model):
|
||||||
|
__database__ = database
|
||||||
|
__metadata__ = metadata
|
||||||
|
|
||||||
|
id = orm.Integer(primary_key=True)
|
||||||
|
name = orm.String(length=100)
|
||||||
|
completed = orm.Boolean(default=False)
|
||||||
|
|
||||||
|
print(Course.__pydantic_model__.__fields__)
|
||||||
|
"""
|
||||||
|
Will produce:
|
||||||
|
{'completed': ModelField(name='completed',
|
||||||
|
type=bool,
|
||||||
|
required=False,
|
||||||
|
default=False),
|
||||||
|
'id': ModelField(name='id',
|
||||||
|
type=Optional[int],
|
||||||
|
required=False,
|
||||||
|
default=None),
|
||||||
|
'name': ModelField(name='name',
|
||||||
|
type=Optional[str],
|
||||||
|
required=False,
|
||||||
|
default=None)}
|
||||||
|
"""
|
||||||
22
docs_src/models/docs004.py
Normal file
22
docs_src/models/docs004.py
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import databases
|
||||||
|
import sqlalchemy
|
||||||
|
|
||||||
|
import orm
|
||||||
|
|
||||||
|
database = databases.Database("sqlite:///db.sqlite")
|
||||||
|
metadata = sqlalchemy.MetaData()
|
||||||
|
|
||||||
|
|
||||||
|
class Course(orm.Model):
|
||||||
|
__database__ = database
|
||||||
|
__metadata__ = metadata
|
||||||
|
|
||||||
|
id = orm.Integer(primary_key=True)
|
||||||
|
name = orm.String(length=100)
|
||||||
|
completed = orm.Boolean(default=False)
|
||||||
|
|
||||||
|
print(Course.__table__.columns)
|
||||||
|
"""
|
||||||
|
Will produce:
|
||||||
|
['courses.id', 'courses.name', 'courses.completed']
|
||||||
|
"""
|
||||||
51
docs_src/models/docs005.py
Normal file
51
docs_src/models/docs005.py
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import databases
|
||||||
|
import sqlalchemy
|
||||||
|
|
||||||
|
import orm
|
||||||
|
|
||||||
|
database = databases.Database("sqlite:///db.sqlite")
|
||||||
|
metadata = sqlalchemy.MetaData()
|
||||||
|
|
||||||
|
|
||||||
|
class Course(orm.Model):
|
||||||
|
__database__ = database
|
||||||
|
__metadata__ = metadata
|
||||||
|
|
||||||
|
id = orm.Integer(primary_key=True)
|
||||||
|
name = orm.String(length=100)
|
||||||
|
completed = orm.Boolean(default=False)
|
||||||
|
|
||||||
|
print(Course.__model_fields__)
|
||||||
|
"""
|
||||||
|
Will produce:
|
||||||
|
{
|
||||||
|
'id': {'name': 'id',
|
||||||
|
'primary_key': True,
|
||||||
|
'autoincrement': True,
|
||||||
|
'nullable': False,
|
||||||
|
'default': None,
|
||||||
|
'server_default': None,
|
||||||
|
'index': None,
|
||||||
|
'unique': None,
|
||||||
|
'pydantic_only': False},
|
||||||
|
'name': {'name': 'name',
|
||||||
|
'primary_key': False,
|
||||||
|
'autoincrement': False,
|
||||||
|
'nullable': True,
|
||||||
|
'default': None,
|
||||||
|
'server_default': None,
|
||||||
|
'index': None,
|
||||||
|
'unique': None,
|
||||||
|
'pydantic_only': False,
|
||||||
|
'length': 100},
|
||||||
|
'completed': {'name': 'completed',
|
||||||
|
'primary_key': False,
|
||||||
|
'autoincrement': False,
|
||||||
|
'nullable': True,
|
||||||
|
'default': False,
|
||||||
|
'server_default': None,
|
||||||
|
'index': None,
|
||||||
|
'unique': None,
|
||||||
|
'pydantic_only': False}
|
||||||
|
}
|
||||||
|
"""
|
||||||
41
docs_src/models/docs006.py
Normal file
41
docs_src/models/docs006.py
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import databases
|
||||||
|
import sqlalchemy
|
||||||
|
|
||||||
|
import orm
|
||||||
|
|
||||||
|
database = databases.Database("sqlite:///db.sqlite")
|
||||||
|
metadata = sqlalchemy.MetaData()
|
||||||
|
|
||||||
|
|
||||||
|
class Department(orm.Model):
|
||||||
|
__database__ = database
|
||||||
|
__metadata__ = metadata
|
||||||
|
|
||||||
|
id = orm.Integer(primary_key=True)
|
||||||
|
name = orm.String(length=100)
|
||||||
|
|
||||||
|
|
||||||
|
class Course(orm.Model):
|
||||||
|
__database__ = database
|
||||||
|
__metadata__ = metadata
|
||||||
|
|
||||||
|
id = orm.Integer(primary_key=True)
|
||||||
|
name = orm.String(length=100)
|
||||||
|
completed = orm.Boolean(default=False)
|
||||||
|
department = orm.ForeignKey(Department)
|
||||||
|
|
||||||
|
|
||||||
|
department = Department(name="Science")
|
||||||
|
course = Course(name="Math", completed=False, department=department)
|
||||||
|
|
||||||
|
print('name' in course.__dict__)
|
||||||
|
# False <- property name is not stored on Course instance
|
||||||
|
print(course.name)
|
||||||
|
# Math <- value returned from underlying pydantic model
|
||||||
|
print('department' in course.__dict__)
|
||||||
|
# False <- related model is not stored on Course instance
|
||||||
|
print(course.department)
|
||||||
|
# Department(id=None, name='Science') <- Department model
|
||||||
|
# returned from RelationshipManager
|
||||||
|
print(course.department.name)
|
||||||
|
# Science
|
||||||
22
docs_src/models/docs007.py
Normal file
22
docs_src/models/docs007.py
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import databases
|
||||||
|
import sqlalchemy
|
||||||
|
|
||||||
|
import orm
|
||||||
|
|
||||||
|
database = databases.Database("sqlite:///db.sqlite")
|
||||||
|
metadata = sqlalchemy.MetaData()
|
||||||
|
|
||||||
|
|
||||||
|
class Course(orm.Model):
|
||||||
|
__database__ = database
|
||||||
|
__metadata__ = metadata
|
||||||
|
|
||||||
|
id = orm.Integer(primary_key=True)
|
||||||
|
name = orm.String(length=100)
|
||||||
|
completed = orm.Boolean(default=False)
|
||||||
|
|
||||||
|
|
||||||
|
course = Course(name="Painting for dummies", completed=False)
|
||||||
|
await course.save()
|
||||||
|
|
||||||
|
await Course.objects.create(name="Painting for dummies", completed=False)
|
||||||
29
mkdocs.yml
Normal file
29
mkdocs.yml
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
site_name: Async ORM
|
||||||
|
nav:
|
||||||
|
- Home: index.md
|
||||||
|
- Models: models.md
|
||||||
|
- Fields: fields.md
|
||||||
|
- Relations: relations.md
|
||||||
|
- Queries: queries.md
|
||||||
|
- Pydantic models: pydantic.md
|
||||||
|
- Use with Fastapi: fastapi.md
|
||||||
|
theme:
|
||||||
|
name: material
|
||||||
|
highlightjs: true
|
||||||
|
hljs_languages:
|
||||||
|
- python
|
||||||
|
palette:
|
||||||
|
primary: indigo
|
||||||
|
markdown_extensions:
|
||||||
|
- admonition
|
||||||
|
- pymdownx.superfences
|
||||||
|
- pymdownx.snippets:
|
||||||
|
base_path: docs
|
||||||
|
- pymdownx.inlinehilite
|
||||||
|
- pymdownx.highlight:
|
||||||
|
linenums: true
|
||||||
|
extra_javascript:
|
||||||
|
- https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/highlight.min.js
|
||||||
|
- javascripts/config.js
|
||||||
|
extra_css:
|
||||||
|
- https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/styles/default.min.css
|
||||||
@ -11,18 +11,8 @@ if TYPE_CHECKING: # pragma no cover
|
|||||||
class BaseField:
|
class BaseField:
|
||||||
__type__ = None
|
__type__ = None
|
||||||
|
|
||||||
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
def __init__(self, **kwargs: Any) -> None:
|
||||||
name = kwargs.pop("name", None)
|
self.name = None
|
||||||
args = list(args)
|
|
||||||
if args:
|
|
||||||
if isinstance(args[0], str):
|
|
||||||
if name is not None:
|
|
||||||
raise ModelDefinitionError(
|
|
||||||
"Column name cannot be passed positionally and as a keyword."
|
|
||||||
)
|
|
||||||
name = args.pop(0)
|
|
||||||
|
|
||||||
self.name = name
|
|
||||||
self._populate_from_kwargs(kwargs)
|
self._populate_from_kwargs(kwargs)
|
||||||
|
|
||||||
def _populate_from_kwargs(self, kwargs: Dict) -> None:
|
def _populate_from_kwargs(self, kwargs: Dict) -> None:
|
||||||
@ -64,7 +54,7 @@ class BaseField:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
def get_column(self, name: str = None) -> sqlalchemy.Column:
|
def get_column(self, name: str = None) -> sqlalchemy.Column:
|
||||||
self.name = self.name or name
|
self.name = name
|
||||||
constraints = self.get_constraints()
|
constraints = self.get_constraints()
|
||||||
return sqlalchemy.Column(
|
return sqlalchemy.Column(
|
||||||
self.name,
|
self.name,
|
||||||
@ -87,3 +77,6 @@ class BaseField:
|
|||||||
|
|
||||||
def expand_relationship(self, value: Any, child: "Model") -> Any:
|
def expand_relationship(self, value: Any, child: "Model") -> Any:
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
def __repr__(self): # pragma no cover
|
||||||
|
return str(self.__dict__)
|
||||||
|
|||||||
@ -14,8 +14,8 @@ class RequiredParams:
|
|||||||
old_init = model_field_class.__init__
|
old_init = model_field_class.__init__
|
||||||
model_field_class._old_init = old_init
|
model_field_class._old_init = old_init
|
||||||
|
|
||||||
def __init__(instance: "BaseField", *args: Any, **kwargs: Any) -> None:
|
def __init__(instance: "BaseField", **kwargs: Any) -> None:
|
||||||
super(instance.__class__, instance).__init__(*args, **kwargs)
|
super(instance.__class__, instance).__init__(**kwargs)
|
||||||
for arg in self._required:
|
for arg in self._required:
|
||||||
if arg not in kwargs:
|
if arg not in kwargs:
|
||||||
raise ModelDefinitionError(
|
raise ModelDefinitionError(
|
||||||
|
|||||||
@ -75,6 +75,8 @@ def sqlalchemy_columns_from_model_fields(
|
|||||||
}
|
}
|
||||||
for field_name, field in model_fields.items():
|
for field_name, field in model_fields.items():
|
||||||
if field.primary_key:
|
if field.primary_key:
|
||||||
|
if pkname is not None:
|
||||||
|
raise ModelDefinitionError("Only one primary key column is allowed.")
|
||||||
pkname = field_name
|
pkname = field_name
|
||||||
if not field.pydantic_only:
|
if not field.pydantic_only:
|
||||||
columns.append(field.get_column(field_name))
|
columns.append(field.get_column(field_name))
|
||||||
@ -100,7 +102,8 @@ class ModelMetaclass(type):
|
|||||||
if attrs.get("__abstract__"):
|
if attrs.get("__abstract__"):
|
||||||
return new_model
|
return new_model
|
||||||
|
|
||||||
tablename = attrs["__tablename__"]
|
tablename = attrs.get("__tablename__", name.lower() + "s")
|
||||||
|
attrs["__tablename__"] = tablename
|
||||||
metadata = attrs["__metadata__"]
|
metadata = attrs["__metadata__"]
|
||||||
|
|
||||||
# sqlalchemy table creation
|
# sqlalchemy table creation
|
||||||
|
|||||||
@ -144,19 +144,21 @@ class QueryClause:
|
|||||||
) -> Tuple[str, bool]:
|
) -> Tuple[str, bool]:
|
||||||
has_escaped_character = False
|
has_escaped_character = False
|
||||||
|
|
||||||
if op in ["contains", "icontains"]:
|
if op not in ["contains", "icontains"]:
|
||||||
if isinstance(value, orm.Model):
|
return value, has_escaped_character
|
||||||
raise QueryDefinitionError(
|
|
||||||
"You cannot use contains and icontains with instance of the Model"
|
|
||||||
)
|
|
||||||
|
|
||||||
has_escaped_character = any(c for c in ESCAPE_CHARACTERS if c in value)
|
if isinstance(value, orm.Model):
|
||||||
|
raise QueryDefinitionError(
|
||||||
|
"You cannot use contains and icontains with instance of the Model"
|
||||||
|
)
|
||||||
|
|
||||||
if has_escaped_character:
|
has_escaped_character = any(c for c in ESCAPE_CHARACTERS if c in value)
|
||||||
# enable escape modifier
|
|
||||||
for char in ESCAPE_CHARACTERS:
|
if has_escaped_character:
|
||||||
value = value.replace(char, f"\\{char}")
|
# enable escape modifier
|
||||||
value = f"%{value}%"
|
for char in ESCAPE_CHARACTERS:
|
||||||
|
value = value.replace(char, f"\\{char}")
|
||||||
|
value = f"%{value}%"
|
||||||
|
|
||||||
return value, has_escaped_character
|
return value, has_escaped_character
|
||||||
|
|
||||||
|
|||||||
@ -52,8 +52,7 @@ class Query:
|
|||||||
if (
|
if (
|
||||||
not self.model_cls.__model_fields__[key].nullable
|
not self.model_cls.__model_fields__[key].nullable
|
||||||
and isinstance(
|
and isinstance(
|
||||||
self.model_cls.__model_fields__[key],
|
self.model_cls.__model_fields__[key], orm.fields.ForeignKey,
|
||||||
orm.fields.foreign_key.ForeignKey,
|
|
||||||
)
|
)
|
||||||
and key not in self._select_related
|
and key not in self._select_related
|
||||||
):
|
):
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import pprint
|
|||||||
import string
|
import string
|
||||||
import uuid
|
import uuid
|
||||||
from random import choices
|
from random import choices
|
||||||
from typing import Dict, List, TYPE_CHECKING, Union
|
from typing import List, TYPE_CHECKING, Union
|
||||||
from weakref import proxy
|
from weakref import proxy
|
||||||
|
|
||||||
from orm import ForeignKey
|
from orm import ForeignKey
|
||||||
@ -15,38 +15,20 @@ def get_table_alias() -> str:
|
|||||||
return "".join(choices(string.ascii_uppercase, k=2)) + uuid.uuid4().hex[:4]
|
return "".join(choices(string.ascii_uppercase, k=2)) + uuid.uuid4().hex[:4]
|
||||||
|
|
||||||
|
|
||||||
def get_relation_config(
|
|
||||||
relation_type: str, table_name: str, field: ForeignKey
|
|
||||||
) -> Dict[str, str]:
|
|
||||||
alias = get_table_alias()
|
|
||||||
config = {
|
|
||||||
"type": relation_type,
|
|
||||||
"table_alias": alias,
|
|
||||||
"source_table": table_name
|
|
||||||
if relation_type == "primary"
|
|
||||||
else field.to.__tablename__,
|
|
||||||
"target_table": field.to.__tablename__
|
|
||||||
if relation_type == "primary"
|
|
||||||
else table_name,
|
|
||||||
}
|
|
||||||
return config
|
|
||||||
|
|
||||||
|
|
||||||
class RelationshipManager:
|
class RelationshipManager:
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self._relations = dict()
|
self._relations = dict()
|
||||||
|
self._aliases = dict()
|
||||||
|
|
||||||
def add_relation_type(
|
def add_relation_type(
|
||||||
self, relations_key: str, reverse_key: str, field: ForeignKey, table_name: str
|
self, relations_key: str, reverse_key: str, field: ForeignKey, table_name: str
|
||||||
) -> None:
|
) -> None:
|
||||||
if relations_key not in self._relations:
|
if relations_key not in self._relations:
|
||||||
self._relations[relations_key] = get_relation_config(
|
self._relations[relations_key] = {"type": "primary"}
|
||||||
"primary", table_name, field
|
self._aliases[f"{table_name}_{field.to.__tablename__}"] = get_table_alias()
|
||||||
)
|
|
||||||
if reverse_key not in self._relations:
|
if reverse_key not in self._relations:
|
||||||
self._relations[reverse_key] = get_relation_config(
|
self._relations[reverse_key] = {"type": "reverse"}
|
||||||
"reverse", table_name, field
|
self._aliases[f"{field.to.__tablename__}_{table_name}"] = get_table_alias()
|
||||||
)
|
|
||||||
|
|
||||||
def deregister(self, model: "FakePydantic") -> None:
|
def deregister(self, model: "FakePydantic") -> None:
|
||||||
for rel_type in self._relations.keys():
|
for rel_type in self._relations.keys():
|
||||||
@ -57,10 +39,11 @@ class RelationshipManager:
|
|||||||
def add_relation(
|
def add_relation(
|
||||||
self, parent: "FakePydantic", child: "FakePydantic", virtual: bool = False,
|
self, parent: "FakePydantic", child: "FakePydantic", virtual: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
parent_id = parent._orm_id
|
parent_id, child_id = parent._orm_id, child._orm_id
|
||||||
child_id = child._orm_id
|
parent_name, child_name = (
|
||||||
parent_name = parent.get_name()
|
parent.get_name(title=True),
|
||||||
child_name = child.get_name()
|
child.get_name(title=True),
|
||||||
|
)
|
||||||
if virtual:
|
if virtual:
|
||||||
child_name, parent_name = parent_name, child_name
|
child_name, parent_name = parent_name, child_name
|
||||||
child_id, parent_id = parent_id, child_id
|
child_id, parent_id = parent_id, child_id
|
||||||
@ -68,11 +51,11 @@ class RelationshipManager:
|
|||||||
else:
|
else:
|
||||||
child = proxy(child)
|
child = proxy(child)
|
||||||
|
|
||||||
parent_relation_name = parent_name.lower().title() + "_" + child_name + "s"
|
parent_relation_name = parent_name + "_" + child_name.lower() + "s"
|
||||||
parents_list = self._relations[parent_relation_name].setdefault(parent_id, [])
|
parents_list = self._relations[parent_relation_name].setdefault(parent_id, [])
|
||||||
self.append_related_model(parents_list, child)
|
self.append_related_model(parents_list, child)
|
||||||
|
|
||||||
child_relation_name = child_name.lower().title() + "_" + parent_name
|
child_relation_name = child_name + "_" + parent_name.lower()
|
||||||
children_list = self._relations[child_relation_name].setdefault(child_id, [])
|
children_list = self._relations[child_relation_name].setdefault(child_id, [])
|
||||||
self.append_related_model(children_list, parent)
|
self.append_related_model(children_list, parent)
|
||||||
|
|
||||||
@ -102,13 +85,7 @@ class RelationshipManager:
|
|||||||
return self._relations[relations_key][instance._orm_id]
|
return self._relations[relations_key][instance._orm_id]
|
||||||
|
|
||||||
def resolve_relation_join(self, from_table: str, to_table: str) -> str:
|
def resolve_relation_join(self, from_table: str, to_table: str) -> str:
|
||||||
for relation_name, relation in self._relations.items():
|
return self._aliases.get(f"{from_table}_{to_table}", "")
|
||||||
if (
|
|
||||||
relation["source_table"] == from_table
|
|
||||||
and relation["target_table"] == to_table
|
|
||||||
):
|
|
||||||
return self._relations[relation_name]["table_alias"]
|
|
||||||
return ""
|
|
||||||
|
|
||||||
def __str__(self) -> str: # pragma no cover
|
def __str__(self) -> str: # pragma no cover
|
||||||
return pprint.pformat(self._relations, indent=4, width=1)
|
return pprint.pformat(self._relations, indent=4, width=1)
|
||||||
|
|||||||
@ -43,8 +43,8 @@ fields_to_check = [
|
|||||||
class ExampleModel2(Model):
|
class ExampleModel2(Model):
|
||||||
__tablename__ = "example2"
|
__tablename__ = "example2"
|
||||||
__metadata__ = metadata
|
__metadata__ = metadata
|
||||||
test = fields.Integer(name="test12", primary_key=True)
|
test = fields.Integer(primary_key=True)
|
||||||
test_string = fields.String("test_string2", length=250)
|
test_string = fields.String(length=250)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture()
|
@pytest.fixture()
|
||||||
@ -93,49 +93,44 @@ def test_sqlalchemy_table_is_created(example):
|
|||||||
assert all([field in example.__table__.columns for field in fields_to_check])
|
assert all([field in example.__table__.columns for field in fields_to_check])
|
||||||
|
|
||||||
|
|
||||||
def test_double_column_name_in_model_definition():
|
|
||||||
with pytest.raises(ModelDefinitionError):
|
|
||||||
|
|
||||||
class ExampleModel2(Model):
|
|
||||||
__tablename__ = "example3"
|
|
||||||
__metadata__ = metadata
|
|
||||||
test_string = fields.String("test_string2", name="test_string2", length=250)
|
|
||||||
|
|
||||||
|
|
||||||
def test_no_pk_in_model_definition():
|
def test_no_pk_in_model_definition():
|
||||||
with pytest.raises(ModelDefinitionError):
|
with pytest.raises(ModelDefinitionError):
|
||||||
|
|
||||||
class ExampleModel2(Model):
|
class ExampleModel2(Model):
|
||||||
__tablename__ = "example3"
|
__tablename__ = "example3"
|
||||||
__metadata__ = metadata
|
__metadata__ = metadata
|
||||||
test_string = fields.String(name="test_string2", length=250)
|
test_string = fields.String(length=250)
|
||||||
|
|
||||||
|
def test_two_pks_in_model_definition():
|
||||||
|
with pytest.raises(ModelDefinitionError):
|
||||||
|
class ExampleModel2(Model):
|
||||||
|
__tablename__ = "example3"
|
||||||
|
__metadata__ = metadata
|
||||||
|
id = fields.Integer(primary_key=True)
|
||||||
|
test_string = fields.String(length=250, primary_key=True)
|
||||||
|
|
||||||
|
|
||||||
def test_setting_pk_column_as_pydantic_only_in_model_definition():
|
def test_setting_pk_column_as_pydantic_only_in_model_definition():
|
||||||
with pytest.raises(ModelDefinitionError):
|
with pytest.raises(ModelDefinitionError):
|
||||||
|
|
||||||
class ExampleModel2(Model):
|
class ExampleModel2(Model):
|
||||||
__tablename__ = "example4"
|
__tablename__ = "example4"
|
||||||
__metadata__ = metadata
|
__metadata__ = metadata
|
||||||
test = fields.Integer(name="test12", primary_key=True, pydantic_only=True)
|
test = fields.Integer(primary_key=True, pydantic_only=True)
|
||||||
|
|
||||||
|
|
||||||
def test_decimal_error_in_model_definition():
|
def test_decimal_error_in_model_definition():
|
||||||
with pytest.raises(ModelDefinitionError):
|
with pytest.raises(ModelDefinitionError):
|
||||||
|
|
||||||
class ExampleModel2(Model):
|
class ExampleModel2(Model):
|
||||||
__tablename__ = "example4"
|
__tablename__ = "example4"
|
||||||
__metadata__ = metadata
|
__metadata__ = metadata
|
||||||
test = fields.Decimal(name="test12", primary_key=True)
|
test = fields.Decimal(primary_key=True)
|
||||||
|
|
||||||
|
|
||||||
def test_string_error_in_model_definition():
|
def test_string_error_in_model_definition():
|
||||||
with pytest.raises(ModelDefinitionError):
|
with pytest.raises(ModelDefinitionError):
|
||||||
|
|
||||||
class ExampleModel2(Model):
|
class ExampleModel2(Model):
|
||||||
__tablename__ = "example4"
|
__tablename__ = "example4"
|
||||||
__metadata__ = metadata
|
__metadata__ = metadata
|
||||||
test = fields.String(name="test12", primary_key=True)
|
test = fields.String(primary_key=True)
|
||||||
|
|
||||||
|
|
||||||
def test_json_conversion_in_model():
|
def test_json_conversion_in_model():
|
||||||
|
|||||||
Reference in New Issue
Block a user