ormlambda 3.12.2__py3-none-any.whl → 3.34.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ormlambda/__init__.py +2 -0
- ormlambda/caster/__init__.py +1 -1
- ormlambda/caster/caster.py +29 -12
- ormlambda/common/abstract_classes/clause_info_converter.py +4 -12
- ormlambda/common/abstract_classes/decomposition_query.py +17 -2
- ormlambda/common/abstract_classes/non_query_base.py +9 -7
- ormlambda/common/abstract_classes/query_base.py +3 -1
- ormlambda/common/errors/__init__.py +29 -0
- ormlambda/common/interfaces/IQueryCommand.py +6 -2
- ormlambda/databases/__init__.py +0 -1
- ormlambda/databases/my_sql/__init__.py +0 -1
- ormlambda/databases/my_sql/caster/caster.py +23 -19
- ormlambda/databases/my_sql/caster/types/__init__.py +3 -0
- ormlambda/databases/my_sql/caster/types/boolean.py +35 -0
- ormlambda/databases/my_sql/caster/types/bytes.py +7 -7
- ormlambda/databases/my_sql/caster/types/date.py +34 -0
- ormlambda/databases/my_sql/caster/types/datetime.py +7 -7
- ormlambda/databases/my_sql/caster/types/decimal.py +32 -0
- ormlambda/databases/my_sql/caster/types/float.py +7 -7
- ormlambda/databases/my_sql/caster/types/int.py +7 -7
- ormlambda/databases/my_sql/caster/types/iterable.py +7 -7
- ormlambda/databases/my_sql/caster/types/none.py +8 -7
- ormlambda/databases/my_sql/caster/types/point.py +4 -4
- ormlambda/databases/my_sql/caster/types/string.py +7 -7
- ormlambda/databases/my_sql/clauses/ST_AsText.py +8 -7
- ormlambda/databases/my_sql/clauses/ST_Contains.py +10 -5
- ormlambda/databases/my_sql/clauses/__init__.py +4 -10
- ormlambda/databases/my_sql/clauses/count.py +5 -15
- ormlambda/databases/my_sql/clauses/delete.py +3 -50
- ormlambda/databases/my_sql/clauses/group_by.py +3 -16
- ormlambda/databases/my_sql/clauses/having.py +2 -6
- ormlambda/databases/my_sql/clauses/insert.py +4 -92
- ormlambda/databases/my_sql/clauses/joins.py +5 -140
- ormlambda/databases/my_sql/clauses/limit.py +4 -15
- ormlambda/databases/my_sql/clauses/offset.py +4 -15
- ormlambda/databases/my_sql/clauses/order.py +4 -61
- ormlambda/databases/my_sql/clauses/update.py +4 -67
- ormlambda/databases/my_sql/clauses/upsert.py +3 -66
- ormlambda/databases/my_sql/clauses/where.py +4 -42
- ormlambda/databases/my_sql/repository.py +217 -0
- ormlambda/dialects/__init__.py +39 -0
- ormlambda/dialects/default/__init__.py +1 -0
- ormlambda/dialects/default/base.py +39 -0
- ormlambda/dialects/interface/__init__.py +1 -0
- ormlambda/dialects/interface/dialect.py +78 -0
- ormlambda/dialects/mysql/__init__.py +8 -0
- ormlambda/dialects/mysql/base.py +387 -0
- ormlambda/dialects/mysql/mysqlconnector.py +46 -0
- ormlambda/dialects/mysql/types.py +732 -0
- ormlambda/dialects/sqlite/__init__.py +5 -0
- ormlambda/dialects/sqlite/base.py +47 -0
- ormlambda/dialects/sqlite/pysqlite.py +32 -0
- ormlambda/engine/__init__.py +1 -0
- ormlambda/engine/base.py +58 -0
- ormlambda/engine/create.py +9 -23
- ormlambda/engine/url.py +31 -19
- ormlambda/env.py +30 -0
- ormlambda/errors.py +17 -0
- ormlambda/model/base_model.py +7 -9
- ormlambda/repository/base_repository.py +36 -5
- ormlambda/repository/interfaces/IRepositoryBase.py +121 -7
- ormlambda/repository/response.py +134 -0
- ormlambda/sql/clause_info/aggregate_function_base.py +19 -9
- ormlambda/sql/clause_info/clause_info.py +34 -17
- ormlambda/sql/clauses/__init__.py +14 -0
- ormlambda/{databases/my_sql → sql}/clauses/alias.py +23 -6
- ormlambda/sql/clauses/count.py +57 -0
- ormlambda/sql/clauses/delete.py +71 -0
- ormlambda/sql/clauses/group_by.py +30 -0
- ormlambda/sql/clauses/having.py +21 -0
- ormlambda/sql/clauses/insert.py +104 -0
- ormlambda/sql/clauses/interfaces/__init__.py +5 -0
- ormlambda/{components → sql/clauses}/join/join_context.py +15 -7
- ormlambda/sql/clauses/joins.py +159 -0
- ormlambda/sql/clauses/limit.py +15 -0
- ormlambda/sql/clauses/offset.py +15 -0
- ormlambda/sql/clauses/order.py +55 -0
- ormlambda/{databases/my_sql → sql}/clauses/select.py +12 -13
- ormlambda/sql/clauses/update.py +84 -0
- ormlambda/sql/clauses/upsert.py +77 -0
- ormlambda/sql/clauses/where.py +65 -0
- ormlambda/sql/column/__init__.py +1 -0
- ormlambda/sql/{column.py → column/column.py} +82 -22
- ormlambda/sql/comparer.py +51 -37
- ormlambda/sql/compiler.py +427 -0
- ormlambda/sql/ddl.py +68 -0
- ormlambda/sql/elements.py +36 -0
- ormlambda/sql/foreign_key.py +43 -39
- ormlambda/{databases/my_sql → sql}/functions/concat.py +13 -5
- ormlambda/{databases/my_sql → sql}/functions/max.py +9 -4
- ormlambda/{databases/my_sql → sql}/functions/min.py +9 -13
- ormlambda/{databases/my_sql → sql}/functions/sum.py +8 -10
- ormlambda/sql/sqltypes.py +647 -0
- ormlambda/sql/table/__init__.py +1 -1
- ormlambda/sql/table/table.py +179 -0
- ormlambda/sql/table/table_constructor.py +1 -208
- ormlambda/sql/type_api.py +35 -0
- ormlambda/sql/types.py +3 -1
- ormlambda/sql/visitors.py +74 -0
- ormlambda/statements/__init__.py +1 -0
- ormlambda/statements/base_statement.py +28 -38
- ormlambda/statements/interfaces/IStatements.py +5 -4
- ormlambda/{databases/my_sql → statements}/query_builder.py +35 -30
- ormlambda/{databases/my_sql → statements}/statements.py +50 -60
- ormlambda/statements/types.py +2 -2
- ormlambda/types/__init__.py +24 -0
- ormlambda/types/metadata.py +42 -0
- ormlambda/util/__init__.py +88 -0
- ormlambda/util/load_module.py +21 -0
- ormlambda/util/plugin_loader.py +32 -0
- ormlambda/util/typing.py +6 -0
- ormlambda-3.34.1.dist-info/AUTHORS +32 -0
- {ormlambda-3.12.2.dist-info → ormlambda-3.34.1.dist-info}/METADATA +2 -3
- ormlambda-3.34.1.dist-info/RECORD +157 -0
- {ormlambda-3.12.2.dist-info → ormlambda-3.34.1.dist-info}/WHEEL +1 -1
- ormlambda/components/__init__.py +0 -4
- ormlambda/components/delete/__init__.py +0 -2
- ormlambda/components/delete/abstract_delete.py +0 -17
- ormlambda/components/insert/__init__.py +0 -2
- ormlambda/components/insert/abstract_insert.py +0 -25
- ormlambda/components/select/__init__.py +0 -1
- ormlambda/components/update/__init__.py +0 -2
- ormlambda/components/update/abstract_update.py +0 -29
- ormlambda/components/upsert/__init__.py +0 -2
- ormlambda/components/upsert/abstract_upsert.py +0 -25
- ormlambda/databases/my_sql/clauses/create_database.py +0 -35
- ormlambda/databases/my_sql/clauses/drop_database.py +0 -17
- ormlambda/databases/my_sql/repository/__init__.py +0 -1
- ormlambda/databases/my_sql/repository/repository.py +0 -351
- ormlambda/engine/template.py +0 -47
- ormlambda/sql/dtypes.py +0 -94
- ormlambda/utils/__init__.py +0 -1
- ormlambda-3.12.2.dist-info/RECORD +0 -125
- /ormlambda/databases/my_sql/{types.py → pool_types.py} +0 -0
- /ormlambda/{components/delete → sql/clauses/interfaces}/IDelete.py +0 -0
- /ormlambda/{components/insert → sql/clauses/interfaces}/IInsert.py +0 -0
- /ormlambda/{components/select → sql/clauses/interfaces}/ISelect.py +0 -0
- /ormlambda/{components/update → sql/clauses/interfaces}/IUpdate.py +0 -0
- /ormlambda/{components/upsert → sql/clauses/interfaces}/IUpsert.py +0 -0
- /ormlambda/{components → sql/clauses}/join/__init__.py +0 -0
- /ormlambda/{databases/my_sql → sql}/functions/__init__.py +0 -0
- /ormlambda/{utils → util}/module_tree/__init__.py +0 -0
- /ormlambda/{utils → util}/module_tree/dfs_traversal.py +0 -0
- /ormlambda/{utils → util}/module_tree/dynamic_module.py +0 -0
- {ormlambda-3.12.2.dist-info → ormlambda-3.34.1.dist-info}/LICENSE +0 -0
@@ -0,0 +1,47 @@
|
|
1
|
+
from ormlambda.sql import compiler
|
2
|
+
from .. import default
|
3
|
+
|
4
|
+
|
5
|
+
class SQLiteCompiler(compiler.SQLCompiler):
|
6
|
+
render_table_with_column_in_update_from = True
|
7
|
+
"""Overridden from base SQLCompiler value"""
|
8
|
+
|
9
|
+
|
10
|
+
class SQLiteDDLCompiler(compiler.DDLCompiler): ...
|
11
|
+
|
12
|
+
|
13
|
+
class SQLiteTypeCompiler(compiler.GenericTypeCompiler):
|
14
|
+
def visit_INTEGER(self, type_, **kw):
|
15
|
+
if self._mysql_type(type_) and type_.display_width is not None:
|
16
|
+
return self._extend_numeric(
|
17
|
+
type_,
|
18
|
+
"INTEGER(%(display_width)s)" % {"display_width": type_.display_width},
|
19
|
+
)
|
20
|
+
else:
|
21
|
+
return self._extend_numeric(type_, "INTEGER")
|
22
|
+
|
23
|
+
def visit_VARCHAR(self, type_, **kw):
|
24
|
+
if type_.length is None:
|
25
|
+
raise ValueError("VARCHAR requires a length on dialect %s" % self.dialect.name)
|
26
|
+
return self._extend_string(type_, {}, "VARCHAR(%d)" % type_.length)
|
27
|
+
|
28
|
+
def visit_CHAR(self, type_, **kw):
|
29
|
+
if type_.length is not None:
|
30
|
+
return self._extend_string(type_, {}, "CHAR(%(length)s)" % {"length": type_.length})
|
31
|
+
else:
|
32
|
+
return self._extend_string(type_, {}, "CHAR")
|
33
|
+
|
34
|
+
|
35
|
+
class SQLiteDialect(default.DefaultDialect):
|
36
|
+
"""Details of the SQLite dialect.
|
37
|
+
Not used directly in application code.
|
38
|
+
"""
|
39
|
+
|
40
|
+
name = "mysql"
|
41
|
+
|
42
|
+
statement_compiler = SQLiteCompiler
|
43
|
+
ddl_compiler = SQLiteDDLCompiler
|
44
|
+
type_compiler_cls = SQLiteTypeCompiler
|
45
|
+
|
46
|
+
def __init__(self, **kwargs):
|
47
|
+
super().__init__(**kwargs)
|
@@ -0,0 +1,32 @@
|
|
1
|
+
from types import ModuleType
|
2
|
+
from ..default import DefaultDialect
|
3
|
+
from ormlambda.sql import compiler
|
4
|
+
|
5
|
+
|
6
|
+
class SQLiteCompiler(compiler.SQLCompiler): ...
|
7
|
+
|
8
|
+
|
9
|
+
class SQLiteDDLCompiler(compiler.DDLCompiler): ...
|
10
|
+
|
11
|
+
|
12
|
+
class SQLiteTypeCompiler(compiler.TypeCompiler): ...
|
13
|
+
|
14
|
+
|
15
|
+
class SQLiteDialect_pysqlite(DefaultDialect):
|
16
|
+
name = "sqlite"
|
17
|
+
|
18
|
+
statement_compiler = SQLiteCompiler
|
19
|
+
ddl_compiler = SQLiteDDLCompiler
|
20
|
+
type_compiler_cls = SQLiteTypeCompiler
|
21
|
+
|
22
|
+
def __init__(self, **kwargs):
|
23
|
+
super().__init__(**kwargs)
|
24
|
+
|
25
|
+
@classmethod
|
26
|
+
def import_dbapi(cls) -> ModuleType:
|
27
|
+
from sqlite3 import dbapi2 as sqlite
|
28
|
+
|
29
|
+
return sqlite
|
30
|
+
|
31
|
+
|
32
|
+
dialect = SQLiteDialect_pysqlite
|
ormlambda/engine/__init__.py
CHANGED
ormlambda/engine/base.py
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
from typing import TYPE_CHECKING, Literal
|
3
|
+
from ormlambda.engine import url
|
4
|
+
from ormlambda.sql.ddl import CreateSchema, DropSchema
|
5
|
+
|
6
|
+
|
7
|
+
if TYPE_CHECKING:
|
8
|
+
from ormlambda.dialects import Dialect
|
9
|
+
|
10
|
+
type TypeExists = Literal["fail", "replace", "append"]
|
11
|
+
|
12
|
+
|
13
|
+
class Engine:
|
14
|
+
def __init__(self, dialect: Dialect, url: url.URL):
|
15
|
+
self.dialect = dialect
|
16
|
+
self.url = url
|
17
|
+
self.repository = self.dialect.repository_cls(url, dialect=dialect)
|
18
|
+
|
19
|
+
def create_schema(self, schema_name: str, if_exists: TypeExists = "fail") -> None:
|
20
|
+
if if_exists == "replace":
|
21
|
+
self.drop_schema(schema_name, if_exists)
|
22
|
+
|
23
|
+
sql = CreateSchema(schema=schema_name, if_not_exists=if_exists== 'append').compile(self.dialect).string
|
24
|
+
try:
|
25
|
+
self.repository.execute(sql)
|
26
|
+
except Exception:
|
27
|
+
if if_exists == "fail":
|
28
|
+
raise
|
29
|
+
|
30
|
+
def drop_schema(self, schema_name: str, if_exists: bool = False) -> None:
|
31
|
+
"""
|
32
|
+
Generate a DROP SCHEMA SQL statement for MySQL.
|
33
|
+
|
34
|
+
Args:
|
35
|
+
schema_name (str): Name of the schema/database to drop
|
36
|
+
if_exists (bool): Whether to include IF EXISTS clause
|
37
|
+
|
38
|
+
Returns:
|
39
|
+
str: The SQL DROP SCHEMA statement
|
40
|
+
|
41
|
+
Raises:
|
42
|
+
ValueError: If schema_name is empty or contains invalid characters
|
43
|
+
"""
|
44
|
+
# if not schema_name or not isinstance(schema_name, str):
|
45
|
+
# raise ValueError("Schema name must be a non-empty string")
|
46
|
+
|
47
|
+
sql = DropSchema(schema=schema_name, if_exists=if_exists).compile(self.dialect).string
|
48
|
+
return self.repository.execute(sql)
|
49
|
+
|
50
|
+
def schema_exists(self, schema: str) -> bool:
|
51
|
+
# sql = SchemaExists(schema).compile(self.dialect).string
|
52
|
+
# return bool(res and len(res) > 0)
|
53
|
+
|
54
|
+
return self.repository.database_exists(schema)
|
55
|
+
|
56
|
+
def set_database(self, name: str) -> None:
|
57
|
+
self.repository.database = name
|
58
|
+
return None
|
ormlambda/engine/create.py
CHANGED
@@ -1,35 +1,21 @@
|
|
1
|
+
from __future__ import annotations
|
1
2
|
from typing import Any
|
2
3
|
|
3
4
|
from ormlambda.engine.url import URL, make_url
|
4
|
-
from ormlambda import BaseRepository
|
5
5
|
|
6
|
+
from . import base
|
6
7
|
|
7
|
-
def create_engine(url: URL | str, **kwargs: Any) -> BaseRepository:
|
8
|
-
from ormlambda.databases import MySQLRepository
|
9
8
|
|
9
|
+
def create_engine(url: URL | str, **kwargs: Any) -> base.Engine:
|
10
10
|
# create url.URL object
|
11
11
|
u = make_url(url)
|
12
12
|
url, kwargs = u._instantiate_plugins(kwargs)
|
13
13
|
|
14
|
-
|
15
|
-
|
16
|
-
}
|
14
|
+
entrypoint = u._get_entrypoint()
|
15
|
+
dialect_cls = entrypoint.get_dialect_cls()
|
17
16
|
|
18
|
-
|
19
|
-
|
17
|
+
dialect_args = {}
|
18
|
+
dialect_args["dbapi"] = dialect_cls.import_dbapi()
|
20
19
|
|
21
|
-
|
22
|
-
|
23
|
-
"password": url.password,
|
24
|
-
"host": url.host,
|
25
|
-
"database": url.database,
|
26
|
-
**kwargs,
|
27
|
-
}
|
28
|
-
|
29
|
-
if url.port:
|
30
|
-
try:
|
31
|
-
default_config["port"] = int(url.port)
|
32
|
-
except ValueError:
|
33
|
-
raise ValueError(f"The port must be an int. '{url.port}' passed.")
|
34
|
-
|
35
|
-
return repo_selector[url.drivername](**default_config)
|
20
|
+
dialect = dialect_cls(**dialect_args)
|
21
|
+
return base.Engine(dialect, u)
|
ormlambda/engine/url.py
CHANGED
@@ -1,23 +1,16 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
1
|
+
# engine/url.py
|
2
|
+
# Copyright (C) 2005-2025 the SQLAlchemy authors and contributors
|
3
|
+
# <see AUTHORS file>
|
4
|
+
#
|
5
|
+
# This module is part of SQLAlchemy and is released under
|
6
|
+
# the MIT License: https://www.opensource.org/licenses/mit-license.php
|
7
|
+
#
|
4
8
|
|
5
9
|
from __future__ import annotations
|
6
10
|
|
7
11
|
import re
|
8
12
|
import collections.abc as collections_abc
|
9
|
-
from typing import
|
10
|
-
Any,
|
11
|
-
Literal,
|
12
|
-
cast,
|
13
|
-
Iterable,
|
14
|
-
Mapping,
|
15
|
-
NamedTuple,
|
16
|
-
Optional,
|
17
|
-
overload,
|
18
|
-
Sequence,
|
19
|
-
Union,
|
20
|
-
)
|
13
|
+
from typing import Any, Literal, Type, cast, Iterable, Mapping, NamedTuple, Optional, overload, Sequence, Union, TYPE_CHECKING
|
21
14
|
|
22
15
|
from urllib.parse import (
|
23
16
|
parse_qsl,
|
@@ -28,7 +21,12 @@ from urllib.parse import (
|
|
28
21
|
|
29
22
|
from . import utils
|
30
23
|
|
31
|
-
|
24
|
+
from ..dialects import registry
|
25
|
+
|
26
|
+
if TYPE_CHECKING:
|
27
|
+
from ..dialects import Dialect
|
28
|
+
|
29
|
+
type DrivernameType = Literal["mysql", "sqlite"] | str
|
32
30
|
|
33
31
|
|
34
32
|
class URL(NamedTuple):
|
@@ -665,6 +663,20 @@ class URL(NamedTuple):
|
|
665
663
|
|
666
664
|
return u, kwargs
|
667
665
|
|
666
|
+
def _get_entrypoint(self) -> Type[Dialect]:
|
667
|
+
"""Return the dialect class for this URL.
|
668
|
+
|
669
|
+
This is the dialect class that corresponds to the database backend
|
670
|
+
in use, and is the portion of the :attr:`_engine.URL.drivername`
|
671
|
+
that is to the left of the plus sign.
|
672
|
+
|
673
|
+
"""
|
674
|
+
if "+" not in self.drivername:
|
675
|
+
name = self.drivername
|
676
|
+
else:
|
677
|
+
name = self.drivername.replace("+", ".")
|
678
|
+
return registry.load(name)
|
679
|
+
|
668
680
|
|
669
681
|
def make_url(name_or_url: str | URL) -> URL:
|
670
682
|
"""Given a string, produce a new URL instance.
|
@@ -684,10 +696,10 @@ def make_url(name_or_url: str | URL) -> URL:
|
|
684
696
|
|
685
697
|
if isinstance(name_or_url, str):
|
686
698
|
return _parse_url(name_or_url)
|
687
|
-
|
699
|
+
if not isinstance(name_or_url, URL) and not hasattr(name_or_url, "_sqla_is_testing_if_this_is_a_mock_object"):
|
688
700
|
raise ValueError(f"Expected string or URL object, got {name_or_url!r}")
|
689
|
-
|
690
|
-
|
701
|
+
|
702
|
+
return name_or_url
|
691
703
|
|
692
704
|
|
693
705
|
def _parse_url(name: str) -> URL:
|
ormlambda/env.py
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
import os
|
2
|
+
from pathlib import Path
|
3
|
+
import logging
|
4
|
+
import sys
|
5
|
+
|
6
|
+
ORMLAMBDA_DIR = Path(__file__).parent
|
7
|
+
SRC_DIR = ORMLAMBDA_DIR.parent
|
8
|
+
BASE_DIR = SRC_DIR.parent
|
9
|
+
|
10
|
+
|
11
|
+
try:
|
12
|
+
from dotenv import find_dotenv, load_dotenv
|
13
|
+
|
14
|
+
load_dotenv(find_dotenv(str(BASE_DIR / ".env")))
|
15
|
+
|
16
|
+
except ImportError:
|
17
|
+
print("dotenv not installed, skipping...")
|
18
|
+
|
19
|
+
|
20
|
+
GLOBAL_LOG_LEVEL = os.getenv("GLOBAL_LOG_LEVEL", "ERROR").upper()
|
21
|
+
logging.basicConfig(
|
22
|
+
level=logging.getLevelNamesMapping().get(GLOBAL_LOG_LEVEL, logging.ERROR),
|
23
|
+
handlers=[
|
24
|
+
logging.StreamHandler(sys.stdout),
|
25
|
+
logging.FileHandler(str(BASE_DIR / "errors.log"), "w", encoding="utf-8"),
|
26
|
+
],
|
27
|
+
)
|
28
|
+
|
29
|
+
log = logging.getLogger(__name__)
|
30
|
+
log.info(f"GLOBAL_LOG_LEVEL: {GLOBAL_LOG_LEVEL}")
|
ormlambda/errors.py
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
class CompileError(Exception):
|
2
|
+
"""Exception raised for errors in the compilation process."""
|
3
|
+
|
4
|
+
def __init__(self, message):
|
5
|
+
super().__init__(message)
|
6
|
+
self.message = message
|
7
|
+
|
8
|
+
def __str__(self):
|
9
|
+
return f"CompileError: {self.message}"
|
10
|
+
|
11
|
+
|
12
|
+
class NoSuchModuleError(Exception):
|
13
|
+
"""Raised when a dynamically-loaded module (usually a database dialect)
|
14
|
+
of a particular name cannot be located."""
|
15
|
+
|
16
|
+
def __str__(self):
|
17
|
+
return f"NoSuchModuleError: {self.args[0]}"
|
ormlambda/model/base_model.py
CHANGED
@@ -3,11 +3,11 @@ from __future__ import annotations
|
|
3
3
|
|
4
4
|
from typing import TYPE_CHECKING, Type
|
5
5
|
|
6
|
-
from ormlambda.
|
6
|
+
from ormlambda.statements import Statements
|
7
|
+
from ormlambda.engine import Engine
|
7
8
|
|
8
9
|
if TYPE_CHECKING:
|
9
10
|
from ormlambda.statements.interfaces import IStatements_two_generic
|
10
|
-
from ormlambda.repository import BaseRepository
|
11
11
|
|
12
12
|
|
13
13
|
# endregion
|
@@ -22,16 +22,14 @@ class BaseModel[T]:
|
|
22
22
|
|
23
23
|
# region Constructor
|
24
24
|
|
25
|
-
def __new__[TPool](cls, model: Type[T],
|
26
|
-
if
|
25
|
+
def __new__[TPool](cls, model: Type[T], engine: Engine) -> IStatements_two_generic[T, TPool]:
|
26
|
+
if engine is None:
|
27
27
|
raise ValueError("`None` cannot be passed to the `repository` attribute when calling the `BaseModel` class")
|
28
28
|
|
29
|
-
|
29
|
+
if not isinstance(engine, Engine):
|
30
|
+
raise ValueError(f"`{engine}` is not a valid `Engine` instance")
|
30
31
|
|
31
|
-
|
32
|
-
raise Exception(f"The selected repository '{repository}' does not exist.")
|
33
|
-
|
34
|
-
return new_cls(model, repository)
|
32
|
+
return Statements(model, engine)
|
35
33
|
|
36
34
|
|
37
35
|
ORM = BaseModel
|
@@ -1,13 +1,44 @@
|
|
1
|
-
import
|
2
|
-
from typing import Generator, Type, Unpack
|
1
|
+
from __future__ import annotations
|
2
|
+
from typing import Generator, Optional, Type, Unpack, TYPE_CHECKING
|
3
3
|
from ormlambda.repository import IRepositoryBase
|
4
4
|
import abc
|
5
5
|
|
6
|
+
if TYPE_CHECKING:
|
7
|
+
from ormlambda import URL as _URL
|
8
|
+
from ormlambda.dialects.interface.dialect import Dialect
|
9
|
+
|
6
10
|
|
7
11
|
class BaseRepository[TPool](IRepositoryBase):
|
8
|
-
def __init__[TArgs](
|
9
|
-
self
|
12
|
+
def __init__[TArgs](
|
13
|
+
self,
|
14
|
+
/,
|
15
|
+
url: Optional[_URL] = None,
|
16
|
+
*,
|
17
|
+
dialect: Optional[Dialect] = None,
|
18
|
+
user: Optional[str] = None,
|
19
|
+
password: Optional[str] = None,
|
20
|
+
host: Optional[str] = None,
|
21
|
+
database: Optional[str] = None,
|
22
|
+
pool: Optional[Type[TPool]] = None,
|
23
|
+
**kwargs: Unpack[TArgs],
|
24
|
+
) -> None:
|
25
|
+
self._dialect = dialect
|
26
|
+
self._url = url
|
27
|
+
|
28
|
+
self._user = user
|
29
|
+
self._password = password
|
30
|
+
self._host = host
|
31
|
+
self._database = database
|
32
|
+
|
33
|
+
if pool:
|
34
|
+
attr = {
|
35
|
+
"user": self._user,
|
36
|
+
"password": self._password,
|
37
|
+
"host": self._host,
|
38
|
+
"database": self._database,
|
39
|
+
**kwargs,
|
40
|
+
}
|
41
|
+
self._pool: TPool = pool(**attr)
|
10
42
|
|
11
|
-
@contextlib.contextmanager
|
12
43
|
@abc.abstractmethod
|
13
44
|
def get_connection[TCnx](self) -> Generator[TCnx, None, None]: ...
|
@@ -1,11 +1,131 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
from abc import ABC, abstractmethod
|
3
|
-
from typing import
|
3
|
+
from typing import (
|
4
|
+
Annotated,
|
5
|
+
Any,
|
6
|
+
Mapping,
|
7
|
+
Optional,
|
8
|
+
Protocol,
|
9
|
+
Sequence,
|
10
|
+
Type,
|
11
|
+
Iterable,
|
12
|
+
TYPE_CHECKING,
|
13
|
+
)
|
4
14
|
|
5
15
|
if TYPE_CHECKING:
|
6
16
|
from ormlambda.statements.types import TypeExists
|
7
17
|
|
8
18
|
|
19
|
+
type _DBAPICursorDescription = Sequence[
|
20
|
+
tuple[
|
21
|
+
Annotated[str, "name"],
|
22
|
+
Annotated[str, "type_code"],
|
23
|
+
Annotated[Optional[str], "display_size"],
|
24
|
+
Annotated[Optional[str], "internal_size"],
|
25
|
+
Annotated[Optional[str], "precision"],
|
26
|
+
Annotated[Optional[str], "scale"],
|
27
|
+
Annotated[Optional[str], "null_ok"],
|
28
|
+
]
|
29
|
+
]
|
30
|
+
|
31
|
+
|
32
|
+
type _GenericSequence = Sequence[Any]
|
33
|
+
|
34
|
+
type _CoreSingleExecuteParams = Mapping[str, Any]
|
35
|
+
type _CoreMultiExecuteParams = Sequence[_CoreSingleExecuteParams]
|
36
|
+
|
37
|
+
type _DBAPISingleExecuteParams = _GenericSequence | _CoreSingleExecuteParams
|
38
|
+
type _DBAPIMultiExecuteParams = Sequence[_GenericSequence] | _CoreMultiExecuteParams
|
39
|
+
|
40
|
+
|
41
|
+
class DBAPICursor(Protocol):
|
42
|
+
"""protocol representing a :pep:`249` database cursor.
|
43
|
+
|
44
|
+
.. versionadded:: 2.0
|
45
|
+
|
46
|
+
.. seealso::
|
47
|
+
|
48
|
+
`Cursor Objects <https://www.python.org/dev/peps/pep-0249/#cursor-objects>`_
|
49
|
+
- in :pep:`249`
|
50
|
+
|
51
|
+
""" # noqa: E501
|
52
|
+
|
53
|
+
@property
|
54
|
+
def description(
|
55
|
+
self,
|
56
|
+
) -> _DBAPICursorDescription:
|
57
|
+
"""The description attribute of the Cursor.
|
58
|
+
|
59
|
+
.. seealso::
|
60
|
+
|
61
|
+
`cursor.description <https://www.python.org/dev/peps/pep-0249/#description>`_
|
62
|
+
- in :pep:`249`
|
63
|
+
|
64
|
+
|
65
|
+
""" # noqa: E501
|
66
|
+
...
|
67
|
+
|
68
|
+
@property
|
69
|
+
def rowcount(self) -> int: ...
|
70
|
+
|
71
|
+
arraysize: int
|
72
|
+
|
73
|
+
lastrowid: int
|
74
|
+
|
75
|
+
def close(self) -> None: ...
|
76
|
+
|
77
|
+
def execute(
|
78
|
+
self,
|
79
|
+
operation: Any,
|
80
|
+
parameters: Optional[_DBAPISingleExecuteParams] = None,
|
81
|
+
) -> Any: ...
|
82
|
+
|
83
|
+
def executemany(
|
84
|
+
self,
|
85
|
+
operation: Any,
|
86
|
+
parameters: _DBAPIMultiExecuteParams,
|
87
|
+
) -> Any: ...
|
88
|
+
|
89
|
+
def fetchone(self) -> Optional[Any]: ...
|
90
|
+
|
91
|
+
def fetchmany(self, size: int = ...) -> Sequence[Any]: ...
|
92
|
+
|
93
|
+
def fetchall(self) -> Sequence[Any]: ...
|
94
|
+
|
95
|
+
def setinputsizes(self, sizes: Sequence[Any]) -> None: ...
|
96
|
+
|
97
|
+
def setoutputsize(self, size: Any, column: Any) -> None: ...
|
98
|
+
|
99
|
+
def callproc(self, procname: str, parameters: Sequence[Any] = ...) -> Any: ...
|
100
|
+
|
101
|
+
def nextset(self) -> Optional[bool]: ...
|
102
|
+
|
103
|
+
def __getattr__(self, key: str) -> Any: ...
|
104
|
+
|
105
|
+
|
106
|
+
class DBAPIConnection(Protocol):
|
107
|
+
"""protocol representing a :pep:`249` database connection.
|
108
|
+
|
109
|
+
.. versionadded:: 2.0
|
110
|
+
|
111
|
+
.. seealso::
|
112
|
+
|
113
|
+
`Connection Objects <https://www.python.org/dev/peps/pep-0249/#connection-objects>`_
|
114
|
+
- in :pep:`249`
|
115
|
+
|
116
|
+
""" # noqa: E501
|
117
|
+
|
118
|
+
def close(self) -> None: ...
|
119
|
+
|
120
|
+
def commit(self) -> None: ...
|
121
|
+
|
122
|
+
def cursor(self) -> DBAPICursor: ...
|
123
|
+
|
124
|
+
def rollback(self) -> None: ...
|
125
|
+
|
126
|
+
autocommit: bool
|
127
|
+
|
128
|
+
|
9
129
|
class IRepositoryBase(ABC):
|
10
130
|
def __repr__(self) -> str:
|
11
131
|
return f"{IRepositoryBase.__name__}: {self.__class__.__name__}"
|
@@ -22,12 +142,6 @@ class IRepositoryBase(ABC):
|
|
22
142
|
@abstractmethod
|
23
143
|
def execute(self, query: str) -> None: ...
|
24
144
|
|
25
|
-
@abstractmethod
|
26
|
-
def drop_database(self, name: str) -> None: ...
|
27
|
-
|
28
|
-
@abstractmethod
|
29
|
-
def create_database(self, name: str, if_exists: TypeExists = "fail") -> None: ...
|
30
|
-
|
31
145
|
@abstractmethod
|
32
146
|
def drop_table(self, name: str) -> None: ...
|
33
147
|
|
@@ -0,0 +1,134 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
from typing import Type, Any, TYPE_CHECKING, Optional
|
3
|
+
from ormlambda.dialects.interface.dialect import Dialect
|
4
|
+
import shapely as shp
|
5
|
+
|
6
|
+
# Custom libraries
|
7
|
+
from ormlambda.sql.clauses import Alias
|
8
|
+
|
9
|
+
if TYPE_CHECKING:
|
10
|
+
from ormlambda.common.abstract_classes.decomposition_query import ClauseInfo
|
11
|
+
from ormlambda import Table
|
12
|
+
from ormlambda.sql.clauses import Select
|
13
|
+
|
14
|
+
|
15
|
+
type TResponse[TFlavour, *Ts] = TFlavour | tuple[dict[str, tuple[*Ts]]] | tuple[tuple[*Ts]] | tuple[TFlavour]
|
16
|
+
|
17
|
+
|
18
|
+
class Response[TFlavour, *Ts]:
|
19
|
+
def __init__(
|
20
|
+
self,
|
21
|
+
dialect: Dialect,
|
22
|
+
response_values: list[tuple[*Ts]],
|
23
|
+
columns: tuple[str],
|
24
|
+
flavour: Type[TFlavour],
|
25
|
+
select: Optional[Select] = None,
|
26
|
+
) -> None:
|
27
|
+
self._dialect: Dialect = dialect
|
28
|
+
self._response_values: list[tuple[*Ts]] = response_values
|
29
|
+
self._columns: tuple[str] = columns
|
30
|
+
self._flavour: Type[TFlavour] = flavour
|
31
|
+
self._select: Select = select
|
32
|
+
|
33
|
+
self._response_values_index: int = len(self._response_values)
|
34
|
+
|
35
|
+
self._caster = dialect.caster()
|
36
|
+
|
37
|
+
@property
|
38
|
+
def is_one(self) -> bool:
|
39
|
+
return self._response_values_index == 1
|
40
|
+
|
41
|
+
@property
|
42
|
+
def is_there_response(self) -> bool:
|
43
|
+
return self._response_values_index != 0
|
44
|
+
|
45
|
+
@property
|
46
|
+
def is_many(self) -> bool:
|
47
|
+
return self._response_values_index > 1
|
48
|
+
|
49
|
+
def response(self, **kwargs) -> TResponse[TFlavour, *Ts]:
|
50
|
+
if not self.is_there_response:
|
51
|
+
return tuple([])
|
52
|
+
|
53
|
+
# Cast data using caster
|
54
|
+
cleaned_response = self._response_values
|
55
|
+
|
56
|
+
if self._select is not None:
|
57
|
+
cleaned_response = self._clean_response()
|
58
|
+
|
59
|
+
cast_flavour = self._cast_to_flavour(cleaned_response, **kwargs)
|
60
|
+
|
61
|
+
return tuple(cast_flavour)
|
62
|
+
|
63
|
+
def _cast_to_flavour(self, data: list[tuple[*Ts]], **kwargs) -> list[dict[str, tuple[*Ts]]] | list[tuple[*Ts]] | list[TFlavour]:
|
64
|
+
def _dict(**kwargs) -> list[dict[str, tuple[*Ts]]]:
|
65
|
+
nonlocal data
|
66
|
+
return [dict(zip(self._columns, x)) for x in data]
|
67
|
+
|
68
|
+
def _tuple(**kwargs) -> list[tuple[*Ts]]:
|
69
|
+
nonlocal data
|
70
|
+
return data
|
71
|
+
|
72
|
+
def _set(**kwargs) -> list[set]:
|
73
|
+
nonlocal data
|
74
|
+
for d in data:
|
75
|
+
n = len(d)
|
76
|
+
for i in range(n):
|
77
|
+
try:
|
78
|
+
hash(d[i])
|
79
|
+
except TypeError:
|
80
|
+
raise TypeError(f"unhashable type '{type(d[i])}' found in '{type(d)}' when attempting to cast the result into a '{set.__name__}' object")
|
81
|
+
return [set(x) for x in data]
|
82
|
+
|
83
|
+
def _list(**kwargs) -> list[list]:
|
84
|
+
nonlocal data
|
85
|
+
return [list(x) for x in data]
|
86
|
+
|
87
|
+
def _default(**kwargs) -> list[TFlavour]:
|
88
|
+
nonlocal data
|
89
|
+
replacer_dicc: dict[str, str] = {}
|
90
|
+
|
91
|
+
for col in self._select.all_clauses:
|
92
|
+
if hasattr(col, "_alias_aggregate") or col.alias_clause is None or isinstance(col, Alias):
|
93
|
+
continue
|
94
|
+
replacer_dicc[col.alias_clause] = col.column
|
95
|
+
|
96
|
+
cleaned_column_names = [replacer_dicc.get(col, col) for col in self._columns]
|
97
|
+
|
98
|
+
result = []
|
99
|
+
for attr in data:
|
100
|
+
dicc_attr = dict(zip(cleaned_column_names, attr))
|
101
|
+
result.append(self._flavour(**dicc_attr, **kwargs))
|
102
|
+
|
103
|
+
return result
|
104
|
+
|
105
|
+
selector: dict[Type[object], Any] = {
|
106
|
+
dict: _dict,
|
107
|
+
tuple: _tuple,
|
108
|
+
set: _set,
|
109
|
+
list: _list,
|
110
|
+
}
|
111
|
+
return selector.get(self._flavour, _default)(**kwargs)
|
112
|
+
|
113
|
+
def _clean_response(self) -> TFlavour:
|
114
|
+
new_response: list[tuple] = []
|
115
|
+
for row in self._response_values:
|
116
|
+
new_row: list = []
|
117
|
+
for i, data in enumerate(row):
|
118
|
+
alias = self._columns[i]
|
119
|
+
clause_info = self._select[alias]
|
120
|
+
parse_data = self._caster.for_value(data, value_type=clause_info.dtype).from_database
|
121
|
+
new_row.append(parse_data)
|
122
|
+
new_row = tuple(new_row)
|
123
|
+
if not isinstance(new_row, tuple):
|
124
|
+
new_row = tuple(new_row)
|
125
|
+
|
126
|
+
new_response.append(new_row)
|
127
|
+
return new_response
|
128
|
+
|
129
|
+
@staticmethod
|
130
|
+
def _is_parser_required[T: Table](clause_info: ClauseInfo[T]) -> bool:
|
131
|
+
if clause_info is None:
|
132
|
+
return False
|
133
|
+
|
134
|
+
return clause_info.dtype is shp.Point
|