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.
Files changed (145) hide show
  1. ormlambda/__init__.py +2 -0
  2. ormlambda/caster/__init__.py +1 -1
  3. ormlambda/caster/caster.py +29 -12
  4. ormlambda/common/abstract_classes/clause_info_converter.py +4 -12
  5. ormlambda/common/abstract_classes/decomposition_query.py +17 -2
  6. ormlambda/common/abstract_classes/non_query_base.py +9 -7
  7. ormlambda/common/abstract_classes/query_base.py +3 -1
  8. ormlambda/common/errors/__init__.py +29 -0
  9. ormlambda/common/interfaces/IQueryCommand.py +6 -2
  10. ormlambda/databases/__init__.py +0 -1
  11. ormlambda/databases/my_sql/__init__.py +0 -1
  12. ormlambda/databases/my_sql/caster/caster.py +23 -19
  13. ormlambda/databases/my_sql/caster/types/__init__.py +3 -0
  14. ormlambda/databases/my_sql/caster/types/boolean.py +35 -0
  15. ormlambda/databases/my_sql/caster/types/bytes.py +7 -7
  16. ormlambda/databases/my_sql/caster/types/date.py +34 -0
  17. ormlambda/databases/my_sql/caster/types/datetime.py +7 -7
  18. ormlambda/databases/my_sql/caster/types/decimal.py +32 -0
  19. ormlambda/databases/my_sql/caster/types/float.py +7 -7
  20. ormlambda/databases/my_sql/caster/types/int.py +7 -7
  21. ormlambda/databases/my_sql/caster/types/iterable.py +7 -7
  22. ormlambda/databases/my_sql/caster/types/none.py +8 -7
  23. ormlambda/databases/my_sql/caster/types/point.py +4 -4
  24. ormlambda/databases/my_sql/caster/types/string.py +7 -7
  25. ormlambda/databases/my_sql/clauses/ST_AsText.py +8 -7
  26. ormlambda/databases/my_sql/clauses/ST_Contains.py +10 -5
  27. ormlambda/databases/my_sql/clauses/__init__.py +4 -10
  28. ormlambda/databases/my_sql/clauses/count.py +5 -15
  29. ormlambda/databases/my_sql/clauses/delete.py +3 -50
  30. ormlambda/databases/my_sql/clauses/group_by.py +3 -16
  31. ormlambda/databases/my_sql/clauses/having.py +2 -6
  32. ormlambda/databases/my_sql/clauses/insert.py +4 -92
  33. ormlambda/databases/my_sql/clauses/joins.py +5 -140
  34. ormlambda/databases/my_sql/clauses/limit.py +4 -15
  35. ormlambda/databases/my_sql/clauses/offset.py +4 -15
  36. ormlambda/databases/my_sql/clauses/order.py +4 -61
  37. ormlambda/databases/my_sql/clauses/update.py +4 -67
  38. ormlambda/databases/my_sql/clauses/upsert.py +3 -66
  39. ormlambda/databases/my_sql/clauses/where.py +4 -42
  40. ormlambda/databases/my_sql/repository.py +217 -0
  41. ormlambda/dialects/__init__.py +39 -0
  42. ormlambda/dialects/default/__init__.py +1 -0
  43. ormlambda/dialects/default/base.py +39 -0
  44. ormlambda/dialects/interface/__init__.py +1 -0
  45. ormlambda/dialects/interface/dialect.py +78 -0
  46. ormlambda/dialects/mysql/__init__.py +8 -0
  47. ormlambda/dialects/mysql/base.py +387 -0
  48. ormlambda/dialects/mysql/mysqlconnector.py +46 -0
  49. ormlambda/dialects/mysql/types.py +732 -0
  50. ormlambda/dialects/sqlite/__init__.py +5 -0
  51. ormlambda/dialects/sqlite/base.py +47 -0
  52. ormlambda/dialects/sqlite/pysqlite.py +32 -0
  53. ormlambda/engine/__init__.py +1 -0
  54. ormlambda/engine/base.py +58 -0
  55. ormlambda/engine/create.py +9 -23
  56. ormlambda/engine/url.py +31 -19
  57. ormlambda/env.py +30 -0
  58. ormlambda/errors.py +17 -0
  59. ormlambda/model/base_model.py +7 -9
  60. ormlambda/repository/base_repository.py +36 -5
  61. ormlambda/repository/interfaces/IRepositoryBase.py +121 -7
  62. ormlambda/repository/response.py +134 -0
  63. ormlambda/sql/clause_info/aggregate_function_base.py +19 -9
  64. ormlambda/sql/clause_info/clause_info.py +34 -17
  65. ormlambda/sql/clauses/__init__.py +14 -0
  66. ormlambda/{databases/my_sql → sql}/clauses/alias.py +23 -6
  67. ormlambda/sql/clauses/count.py +57 -0
  68. ormlambda/sql/clauses/delete.py +71 -0
  69. ormlambda/sql/clauses/group_by.py +30 -0
  70. ormlambda/sql/clauses/having.py +21 -0
  71. ormlambda/sql/clauses/insert.py +104 -0
  72. ormlambda/sql/clauses/interfaces/__init__.py +5 -0
  73. ormlambda/{components → sql/clauses}/join/join_context.py +15 -7
  74. ormlambda/sql/clauses/joins.py +159 -0
  75. ormlambda/sql/clauses/limit.py +15 -0
  76. ormlambda/sql/clauses/offset.py +15 -0
  77. ormlambda/sql/clauses/order.py +55 -0
  78. ormlambda/{databases/my_sql → sql}/clauses/select.py +12 -13
  79. ormlambda/sql/clauses/update.py +84 -0
  80. ormlambda/sql/clauses/upsert.py +77 -0
  81. ormlambda/sql/clauses/where.py +65 -0
  82. ormlambda/sql/column/__init__.py +1 -0
  83. ormlambda/sql/{column.py → column/column.py} +82 -22
  84. ormlambda/sql/comparer.py +51 -37
  85. ormlambda/sql/compiler.py +427 -0
  86. ormlambda/sql/ddl.py +68 -0
  87. ormlambda/sql/elements.py +36 -0
  88. ormlambda/sql/foreign_key.py +43 -39
  89. ormlambda/{databases/my_sql → sql}/functions/concat.py +13 -5
  90. ormlambda/{databases/my_sql → sql}/functions/max.py +9 -4
  91. ormlambda/{databases/my_sql → sql}/functions/min.py +9 -13
  92. ormlambda/{databases/my_sql → sql}/functions/sum.py +8 -10
  93. ormlambda/sql/sqltypes.py +647 -0
  94. ormlambda/sql/table/__init__.py +1 -1
  95. ormlambda/sql/table/table.py +179 -0
  96. ormlambda/sql/table/table_constructor.py +1 -208
  97. ormlambda/sql/type_api.py +35 -0
  98. ormlambda/sql/types.py +3 -1
  99. ormlambda/sql/visitors.py +74 -0
  100. ormlambda/statements/__init__.py +1 -0
  101. ormlambda/statements/base_statement.py +28 -38
  102. ormlambda/statements/interfaces/IStatements.py +5 -4
  103. ormlambda/{databases/my_sql → statements}/query_builder.py +35 -30
  104. ormlambda/{databases/my_sql → statements}/statements.py +50 -60
  105. ormlambda/statements/types.py +2 -2
  106. ormlambda/types/__init__.py +24 -0
  107. ormlambda/types/metadata.py +42 -0
  108. ormlambda/util/__init__.py +88 -0
  109. ormlambda/util/load_module.py +21 -0
  110. ormlambda/util/plugin_loader.py +32 -0
  111. ormlambda/util/typing.py +6 -0
  112. ormlambda-3.34.1.dist-info/AUTHORS +32 -0
  113. {ormlambda-3.12.2.dist-info → ormlambda-3.34.1.dist-info}/METADATA +2 -3
  114. ormlambda-3.34.1.dist-info/RECORD +157 -0
  115. {ormlambda-3.12.2.dist-info → ormlambda-3.34.1.dist-info}/WHEEL +1 -1
  116. ormlambda/components/__init__.py +0 -4
  117. ormlambda/components/delete/__init__.py +0 -2
  118. ormlambda/components/delete/abstract_delete.py +0 -17
  119. ormlambda/components/insert/__init__.py +0 -2
  120. ormlambda/components/insert/abstract_insert.py +0 -25
  121. ormlambda/components/select/__init__.py +0 -1
  122. ormlambda/components/update/__init__.py +0 -2
  123. ormlambda/components/update/abstract_update.py +0 -29
  124. ormlambda/components/upsert/__init__.py +0 -2
  125. ormlambda/components/upsert/abstract_upsert.py +0 -25
  126. ormlambda/databases/my_sql/clauses/create_database.py +0 -35
  127. ormlambda/databases/my_sql/clauses/drop_database.py +0 -17
  128. ormlambda/databases/my_sql/repository/__init__.py +0 -1
  129. ormlambda/databases/my_sql/repository/repository.py +0 -351
  130. ormlambda/engine/template.py +0 -47
  131. ormlambda/sql/dtypes.py +0 -94
  132. ormlambda/utils/__init__.py +0 -1
  133. ormlambda-3.12.2.dist-info/RECORD +0 -125
  134. /ormlambda/databases/my_sql/{types.py → pool_types.py} +0 -0
  135. /ormlambda/{components/delete → sql/clauses/interfaces}/IDelete.py +0 -0
  136. /ormlambda/{components/insert → sql/clauses/interfaces}/IInsert.py +0 -0
  137. /ormlambda/{components/select → sql/clauses/interfaces}/ISelect.py +0 -0
  138. /ormlambda/{components/update → sql/clauses/interfaces}/IUpdate.py +0 -0
  139. /ormlambda/{components/upsert → sql/clauses/interfaces}/IUpsert.py +0 -0
  140. /ormlambda/{components → sql/clauses}/join/__init__.py +0 -0
  141. /ormlambda/{databases/my_sql → sql}/functions/__init__.py +0 -0
  142. /ormlambda/{utils → util}/module_tree/__init__.py +0 -0
  143. /ormlambda/{utils → util}/module_tree/dfs_traversal.py +0 -0
  144. /ormlambda/{utils → util}/module_tree/dynamic_module.py +0 -0
  145. {ormlambda-3.12.2.dist-info → ormlambda-3.34.1.dist-info}/LICENSE +0 -0
@@ -0,0 +1,5 @@
1
+ from . import base
2
+ from . import pysqlite
3
+
4
+ # default dialect
5
+ base.dialect = dialect = pysqlite.dialect
@@ -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
@@ -1,2 +1,3 @@
1
1
  from .create import create_engine # noqa: F401
2
2
  from .url import URL, make_url # noqa: F401
3
+ from .base import Engine # noqa: F401
@@ -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
@@ -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
- repo_selector = {
15
- "mysql": MySQLRepository,
16
- }
14
+ entrypoint = u._get_entrypoint()
15
+ dialect_cls = entrypoint.get_dialect_cls()
17
16
 
18
- if url.drivername not in repo_selector:
19
- raise ValueError(f"drivername '{url.drivername}' not expected to load Repository class")
17
+ dialect_args = {}
18
+ dialect_args["dbapi"] = dialect_cls.import_dbapi()
20
19
 
21
- default_config = {
22
- "user": url.username,
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
- URL class extracted from SQLAlchemy
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
- type DrivernameType = Literal["mysql"] | str
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
- elif not isinstance(name_or_url, URL) and not hasattr(name_or_url, "_sqla_is_testing_if_this_is_a_mock_object"):
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
- else:
690
- return name_or_url
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]}"
@@ -3,11 +3,11 @@ from __future__ import annotations
3
3
 
4
4
  from typing import TYPE_CHECKING, Type
5
5
 
6
- from ormlambda.engine.template import RepositoryTemplateDict
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], repository: BaseRepository[TPool]) -> IStatements_two_generic[T, TPool]:
26
- if repository is None:
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
- new_cls = RepositoryTemplateDict().get(repository).statement
29
+ if not isinstance(engine, Engine):
30
+ raise ValueError(f"`{engine}` is not a valid `Engine` instance")
30
31
 
31
- if not new_cls:
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 contextlib
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](self, pool: Type[TPool], **kwargs: Unpack[TArgs]) -> None:
9
- self._pool: TPool = pool(**kwargs)
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 Optional, Type, Iterable, TYPE_CHECKING
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