ormlambda 2.7.2__py3-none-any.whl → 2.9.0__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.
@@ -1,7 +1,8 @@
1
1
  from __future__ import annotations
2
2
  from pathlib import Path
3
- from typing import Any, Optional, Type, override, Callable
3
+ from typing import Any, Optional, Type, override, Callable, TYPE_CHECKING
4
4
  import functools
5
+ import shapely as shp
5
6
 
6
7
  # from mysql.connector.pooling import MySQLConnectionPool
7
8
  from mysql.connector import MySQLConnection, Error # noqa: F401
@@ -16,12 +17,20 @@ from .clauses import DropDatabase
16
17
  from .clauses import DropTable
17
18
 
18
19
 
20
+ if TYPE_CHECKING:
21
+ from src.ormlambda.common.abstract_classes.decomposition_query import ClauseInfo
22
+ from ormlambda import Table
23
+ from src.ormlambda.databases.my_sql.clauses.select import Select
24
+
25
+
19
26
  class Response[TFlavour, *Ts]:
20
- def __init__(self, response_values: list[tuple[*Ts]], columns: tuple[str], flavour: Type[TFlavour], **kwargs) -> None:
27
+ def __init__(self, response_values: list[tuple[*Ts]], columns: tuple[str], flavour: Type[TFlavour], model: Optional[Table] = None, select: Optional[Select] = None, **kwargs) -> None:
21
28
  self._response_values: list[tuple[*Ts]] = response_values
22
29
  self._columns: tuple[str] = columns
23
30
  self._flavour: Type[TFlavour] = flavour
24
31
  self._kwargs: dict[str, Any] = kwargs
32
+ self._model: Table = model
33
+ self._select: Select = select
25
34
 
26
35
  self._response_values_index: int = len(self._response_values)
27
36
  # self.select_values()
@@ -42,8 +51,10 @@ class Response[TFlavour, *Ts]:
42
51
  def response(self) -> tuple[dict[str, tuple[*Ts]]] | tuple[tuple[*Ts]] | tuple[TFlavour]:
43
52
  if not self.is_there_response:
44
53
  return tuple([])
45
-
46
- return tuple(self._cast_to_flavour(self._response_values))
54
+ clean_response = self._response_values
55
+ if self._select is not None:
56
+ clean_response = self._parser_response()
57
+ return tuple(self._cast_to_flavour(clean_response))
47
58
 
48
59
  def _cast_to_flavour(self, data: list[tuple[*Ts]]) -> list[dict[str, tuple[*Ts]]] | list[tuple[*Ts]] | list[TFlavour]:
49
60
  def _dict() -> list[dict[str, tuple[*Ts]]]:
@@ -73,6 +84,38 @@ class Response[TFlavour, *Ts]:
73
84
 
74
85
  return selector.get(self._flavour, _default)()
75
86
 
87
+ def _parser_response(self) -> TFlavour:
88
+ new_response: list[list] = []
89
+ for row in self._response_values:
90
+ new_row: list = []
91
+ for i, data in enumerate(row):
92
+ alias = self._columns[i]
93
+ clause_info = self._select[alias]
94
+ if not self._is_parser_required(clause_info):
95
+ new_row = row
96
+ break
97
+ else:
98
+ parser_data = self.parser_data(clause_info, data)
99
+ new_row.append(parser_data)
100
+ if not isinstance(new_row, tuple):
101
+ new_row = tuple(new_row)
102
+
103
+ new_response.append(new_row)
104
+ return new_response
105
+
106
+ @staticmethod
107
+ def _is_parser_required[T: Table](clause_info: ClauseInfo[T]) -> bool:
108
+ if clause_info is None:
109
+ return False
110
+
111
+ return clause_info.dtype is shp.Point
112
+
113
+ @staticmethod
114
+ def parser_data[T: Table, TProp](clause_info: ClauseInfo[T], data: TProp):
115
+ if clause_info.dtype is shp.Point:
116
+ return shp.from_wkt(data)
117
+ return data
118
+
76
119
 
77
120
  class MySQLRepository(IRepositoryBase[MySQLConnection]):
78
121
  def get_connection(func: Callable[..., Any]):
@@ -107,11 +150,19 @@ class MySQLRepository(IRepositoryBase[MySQLConnection]):
107
150
  - flavour: Type[TFlavour]: Useful to return tuple of any Iterable type as dict,set,list...
108
151
  """
109
152
 
153
+ def get_and_drop_key(key: str) -> Optional[Any]:
154
+ if key in kwargs:
155
+ return kwargs.pop(key)
156
+ return None
157
+
158
+ model: Table = get_and_drop_key("model")
159
+ select: Select = get_and_drop_key("select")
160
+
110
161
  with cnx.cursor(buffered=True) as cursor:
111
162
  cursor.execute(query)
112
163
  values: list[tuple] = cursor.fetchall()
113
164
  columns: tuple[str] = cursor.column_names
114
- return Response[TFlavour](response_values=values, columns=columns, flavour=flavour, **kwargs).response
165
+ return Response[TFlavour](model=model, response_values=values, columns=columns, flavour=flavour, select=select, **kwargs).response
115
166
 
116
167
  # FIXME [ ]: this method does not comply with the implemented interface
117
168
  @get_connection
@@ -192,7 +243,6 @@ class MySQLRepository(IRepositoryBase[MySQLConnection]):
192
243
  def create_database(self, name: str, if_exists: TypeExists = "fail") -> None:
193
244
  return CreateDatabase(self).execute(name, if_exists)
194
245
 
195
-
196
246
  @property
197
247
  def database(self) -> Optional[str]:
198
248
  return self._data_config.get("database", None)
@@ -2,12 +2,12 @@ from __future__ import annotations
2
2
  from typing import Iterable, override, Type, TYPE_CHECKING, Any, Callable, Optional
3
3
  import inspect
4
4
  from mysql.connector import MySQLConnection, errors, errorcode
5
-
5
+ import functools
6
6
 
7
7
  if TYPE_CHECKING:
8
8
  from ormlambda import Table
9
9
  from ormlambda.components.where.abstract_where import AbstractWhere
10
- from ormlambda.common.interfaces.IStatements import OrderType
10
+ from ormlambda.common.interfaces.IStatements import OrderTypes
11
11
  from ormlambda.common.interfaces import IQuery, IRepositoryBase, IStatements_two_generic
12
12
  from ormlambda.common.interfaces.IRepositoryBase import TypeExists
13
13
  from ormlambda.common.interfaces import IAggregate
@@ -34,6 +34,18 @@ from ormlambda.common.enums import JoinType
34
34
  from . import functions as func
35
35
 
36
36
 
37
+ # COMMENT: It's so important to prevent information generated by other tests from being retained in the class.
38
+ def clear_list(f: Callable[..., Any]):
39
+ @functools.wraps(f)
40
+ def wrapper(self: MySQLStatements, *args, **kwargs):
41
+ try:
42
+ return f(self, *args, **kwargs)
43
+ finally:
44
+ self._query_list.clear()
45
+
46
+ return wrapper
47
+
48
+
37
49
  class MySQLStatements[T: Table](AbstractSQLStatements[T, MySQLConnection]):
38
50
  def __init__(self, model: T, repository: IRepositoryBase[MySQLConnection]) -> None:
39
51
  super().__init__(model, repository=repository)
@@ -71,11 +83,11 @@ class MySQLStatements[T: Table](AbstractSQLStatements[T, MySQLConnection]):
71
83
  return self._repository.table_exists(self._model.__table_name__)
72
84
 
73
85
  @override
86
+ @clear_list
74
87
  def insert(self, instances: T | list[T]) -> None:
75
88
  insert = InsertQuery(self._model, self._repository)
76
89
  insert.insert(instances)
77
90
  insert.execute()
78
- self._query_list.clear()
79
91
  return None
80
92
 
81
93
  @override
@@ -95,44 +107,43 @@ class MySQLStatements[T: Table](AbstractSQLStatements[T, MySQLConnection]):
95
107
  return None
96
108
 
97
109
  @override
110
+ @clear_list
98
111
  def upsert(self, instances: T | list[T]) -> None:
99
112
  upsert = UpsertQuery(self._model, self._repository)
100
113
  upsert.upsert(instances)
101
114
  upsert.execute()
102
- self._query_list.clear()
103
115
  return None
104
116
 
105
117
  @override
118
+ @clear_list
106
119
  def update(self, dicc: dict[str, Any] | list[dict[str, Any]]) -> None:
107
120
  update = UpdateQuery(self._model, self._repository, self._query_list["where"])
108
121
  update.update(dicc)
109
122
  update.execute()
110
- self._query_list.clear()
123
+
111
124
  return None
112
125
 
113
126
  @override
114
127
  def limit(self, number: int) -> IStatements_two_generic[T, MySQLConnection]:
115
128
  limit = LimitQuery(number)
116
129
  # Only can be one LIMIT SQL parameter. We only use the last LimitQuery
117
- limit_list = self._query_list["limit"]
118
- if len(limit_list) > 0:
119
- self._query_list["limit"] = [limit]
120
- else:
121
- self._query_list["limit"].append(limit)
130
+ self._query_list["limit"] = [limit]
122
131
  return self
123
132
 
124
133
  @override
125
134
  def offset(self, number: int) -> IStatements_two_generic[T, MySQLConnection]:
126
135
  offset = OffsetQuery(number)
127
- self._query_list["offset"].append(offset)
136
+ self._query_list["offset"] = [offset]
128
137
  return self
129
138
 
130
139
  @override
131
- def count(self, selection: Callable[[T], tuple] = lambda x: "*") -> int:
132
- count_select: IQuery = Select[T](self._model, lambda x: Count[T](self._model, selection))
133
- self._query_list["select"].append(count_select)
134
- query = self._build()
135
- return self.repository.read_sql(query)[0][0]
140
+ def count(
141
+ self,
142
+ selection: Callable[[T], tuple] = lambda x: "*",
143
+ alias=True,
144
+ alias_name=None,
145
+ ) -> IQuery:
146
+ return Count[T](self._model, selection, alias=alias, alias_name=alias_name)
136
147
 
137
148
  @override
138
149
  def join(self, table_left: Table, table_right: Table, *, by: str) -> IStatements_two_generic[T, MySQLConnection]:
@@ -155,7 +166,7 @@ class MySQLStatements[T: Table](AbstractSQLStatements[T, MySQLConnection]):
155
166
  return self
156
167
 
157
168
  @override
158
- def order[TValue](self, _lambda_col: Callable[[T], TValue], order_type: OrderType) -> IStatements_two_generic[T, MySQLConnection]:
169
+ def order[TValue](self, _lambda_col: Callable[[T], TValue], order_type: OrderTypes) -> IStatements_two_generic[T, MySQLConnection]:
159
170
  order = OrderQuery[T](self._model, _lambda_col, order_type)
160
171
  self._query_list["order"].append(order)
161
172
  return self
@@ -172,6 +183,10 @@ class MySQLStatements[T: Table](AbstractSQLStatements[T, MySQLConnection]):
172
183
  def min[TProp](self, column: Callable[[T], TProp], alias: bool = True, alias_name: str = "min") -> TProp:
173
184
  return func.Min[T](self._model, column=column, alias=alias, alias_name=alias_name)
174
185
 
186
+ @override
187
+ def sum[TProp](self, column: Callable[[T], TProp], alias: bool = True, alias_name: str = "sum") -> TProp:
188
+ return func.Sum[T](self._model, column=column, alias=alias, alias_name=alias_name)
189
+
175
190
  @override
176
191
  def select[TValue, TFlavour, *Ts](self, selector: Optional[Callable[[T], tuple[TValue, *Ts]]] = lambda: None, *, flavour: Optional[Type[TFlavour]] = None, by: JoinType = JoinType.INNER_JOIN):
177
192
  if len(inspect.signature(selector).parameters) == 0:
@@ -187,7 +202,7 @@ class MySQLStatements[T: Table](AbstractSQLStatements[T, MySQLConnection]):
187
202
 
188
203
  query: str = self._build()
189
204
  if flavour:
190
- result = self._return_flavour(query, flavour)
205
+ result = self._return_flavour(query, flavour, select)
191
206
  if issubclass(flavour, tuple) and isinstance(selector(self._model), property):
192
207
  return tuple([x[0] for x in result])
193
208
  return result
@@ -211,13 +226,19 @@ class MySQLStatements[T: Table](AbstractSQLStatements[T, MySQLConnection]):
211
226
  return tuple([res[0] for res in response])
212
227
 
213
228
  @override
214
- def group_by[TRepo, *Ts](self, column: Callable[[T], TRepo], select_query: Callable[[T], tuple[*Ts]]) -> tuple[tuple[*Ts]]:
215
- return GroupBy[T, TRepo, tuple[*Ts]](self._model, column, select_query)
229
+ def group_by[*Ts](self, column: str | Callable[[T], Any]) -> IStatements_two_generic[T, MySQLConnection]:
230
+ if isinstance(column, str):
231
+ groupby = GroupBy[T, tuple[*Ts]](self._model, lambda x: column)
232
+ else:
233
+ groupby = GroupBy[T, tuple[*Ts]](self._model, column)
234
+ # Only can be one LIMIT SQL parameter. We only use the last LimitQuery
235
+ self._query_list["group by"].append(groupby)
236
+ return self
216
237
 
217
238
  @override
239
+ @clear_list
218
240
  def _build(self) -> str:
219
- query: str = ""
220
-
241
+ query_list: list[str] = []
221
242
  for x in self.__order__:
222
243
  sub_query: Optional[list[IQuery]] = self._query_list.get(x, None)
223
244
  if sub_query is None:
@@ -244,9 +265,8 @@ class MySQLStatements[T: Table](AbstractSQLStatements[T, MySQLConnection]):
244
265
  else:
245
266
  query_ = "\n".join([x.query for x in sub_query])
246
267
 
247
- query += f"\n{query_}" if query != "" else query_
248
- self._query_list.clear()
249
- return query
268
+ query_list.append(query_)
269
+ return "\n".join(query_list)
250
270
 
251
271
  def __build_where_clause(self, where_condition: list[AbstractWhere]) -> str:
252
272
  query: str = where_condition[0].query
ormlambda/utils/column.py CHANGED
@@ -1,7 +1,14 @@
1
- from typing import Type
1
+ from __future__ import annotations
2
+ from typing import Type, Optional, Callable, TYPE_CHECKING, Any
3
+ import shapely as sph
4
+
5
+ if TYPE_CHECKING:
6
+ from .table_constructor import Field
2
7
 
3
8
 
4
9
  class Column[T]:
10
+ CHAR: str = "%s"
11
+
5
12
  __slots__ = (
6
13
  "dtype",
7
14
  "column_name",
@@ -31,22 +38,54 @@ class Column[T]:
31
38
  self.is_auto_increment: bool = is_auto_increment
32
39
  self.is_unique: bool = is_unique
33
40
 
41
+ @property
42
+ def column_value_to_query(self) -> T:
43
+ """
44
+ This property must ensure that any variable requiring casting by different database methods is properly wrapped.
45
+ """
46
+ if self.dtype is sph.Point:
47
+ return sph.to_wkt(self.column_value, -1)
48
+ return self.column_value
49
+
50
+ @property
51
+ def placeholder(self) -> str:
52
+ return self.placeholder_resolutor(self.dtype)
53
+
54
+ @property
55
+ def placeholder_resolutor(self) -> Callable[[Type, T], str]:
56
+ return self.__fetch_wrapped_method
57
+
58
+ # FIXME [ ]: this method is allocating the Column class with MySQL database
59
+ @classmethod
60
+ def __fetch_wrapped_method(cls, type_: Type) -> Optional[str]:
61
+ """
62
+ This method must ensure that any variable requiring casting by different database methods is properly wrapped.
63
+ """
64
+ caster: dict[Type[Any], Callable[[str], str]] = {
65
+ sph.Point: lambda x: f"ST_GeomFromText({x})",
66
+ }
67
+ return caster.get(type_, lambda x: x)(cls.CHAR)
68
+
34
69
  def __repr__(self) -> str:
35
- return f"<Column: {self.column_name}>"
70
+ return f"<Column: {self.dtype}>"
36
71
 
37
- def __to_string__(self, name: str, var_name: T, type_: str):
38
- dicc: dict = {
39
- "dtype": type_,
40
- "column_name": f"'{name}'",
41
- "column_value": var_name, # must be the same variable name as the instance variable name in Table's __init__ class
72
+ def __to_string__(self, field: Field):
73
+ column_class_string: str = f"{Column.__name__}[{field.type_name}]("
74
+
75
+ dicc: dict[str, Callable[[Field], str]] = {
76
+ "dtype": lambda field: field.type_name,
77
+ "column_name": lambda field: f"'{field.name}'",
78
+ "column_value": lambda field: field.name, # must be the same variable name as the instance variable name in Table's __init__ class
42
79
  }
43
- exec_str: str = f"{Column.__name__}[{type_}]("
44
- for x in self.__slots__:
45
- self_value = getattr(self, x)
80
+ for self_var in self.__init__.__annotations__:
81
+ if not hasattr(self, self_var):
82
+ continue
83
+
84
+ self_value = dicc.get(self_var, lambda field: getattr(self, self_var))(field)
85
+ column_class_string += f" {self_var}={self_value}, "
46
86
 
47
- exec_str += f" {x}={dicc.get(x,self_value)},\n"
48
- exec_str += ")"
49
- return exec_str
87
+ column_class_string += ")"
88
+ return column_class_string
50
89
 
51
90
  def __hash__(self) -> int:
52
91
  return hash(
ormlambda/utils/dtypes.py CHANGED
@@ -48,8 +48,10 @@ MySQL 8.0 does not support year in two-digit format.
48
48
  """
49
49
 
50
50
  from decimal import Decimal
51
- import datetime
52
51
  from typing import Any, Literal
52
+ import datetime
53
+
54
+ from shapely import Point
53
55
  import numpy as np
54
56
 
55
57
  from .column import Column
@@ -66,17 +68,7 @@ DATE = Literal["DATE", "DATETIME(fsp)", "TIMESTAMP(fsp)", "TIME(fsp)", "YEAR"]
66
68
  def transform_py_dtype_into_query_dtype(dtype: Any) -> str:
67
69
  # TODOL: must be found a better way to convert python data type into SQL clauses
68
70
  # float -> DECIMAL(5,2) is an error
69
- dicc: dict[Any, str] = {
70
- int: "INTEGER",
71
- float: "FLOAT(5,2)",
72
- Decimal: "FLOAT",
73
- datetime.datetime: "DATETIME",
74
- datetime.date: "DATE",
75
- bytes: "BLOB",
76
- bytearray: "BLOB",
77
- str: "VARCHAR(255)",
78
- np.uint64: "BIGINT UNSIGNED",
79
- }
71
+ dicc: dict[Any, str] = {int: "INTEGER", float: "FLOAT(5,2)", Decimal: "FLOAT", datetime.datetime: "DATETIME", datetime.date: "DATE", bytes: "BLOB", bytearray: "BLOB", str: "VARCHAR(255)", np.uint64: "BIGINT UNSIGNED", Point: "Point"}
80
72
 
81
73
  res = dicc.get(dtype, None)
82
74
  if res is None:
@@ -0,0 +1,60 @@
1
+ import typing as tp
2
+ from .column import Column
3
+
4
+ __all__ = ["get_fields"]
5
+
6
+ MISSING = lambda: Column() # COMMENT: Very Important to avoid reusing the same variable across different classes. # noqa: E731
7
+
8
+
9
+ class Field[TProp: tp.AnnotatedAny]:
10
+ def __init__(self, name: str, type_: tp.Type, default: Column[TProp]) -> None:
11
+ self.name: str = name
12
+ self.type_: tp.Type[TProp] = type_
13
+ self.default: Column[TProp] = default
14
+
15
+ def __repr__(self) -> str:
16
+ return f"{Field.__name__}(name = {self.name}, type_ = {self.type_}, default = {self.default})"
17
+
18
+ @property
19
+ def has_default(self) -> bool:
20
+ return self.default is not MISSING()
21
+
22
+ @property
23
+ def init_arg(self) -> str:
24
+ default = f"={self.default_name}" # if self.has_default else ""}"
25
+ return f"{self.name}: {self.type_name}{default}"
26
+
27
+ @property
28
+ def default_name(self) -> str:
29
+ return f"_dflt_{self.name}"
30
+
31
+ @property
32
+ def type_name(self) -> str:
33
+ return f"_type_{self.name}"
34
+
35
+ @property
36
+ def assginment(self) -> str:
37
+ return f"self._{self.name} = {self.default.__to_string__(self)}"
38
+
39
+
40
+ def get_fields[T, TProp](cls: tp.Type[T]) -> tp.Iterable[Field]:
41
+ # COMMENT: Used the 'get_type_hints' method to resolve typing when 'from __future__ import annotations' is in use
42
+ annotations = {key: val for key, val in tp.get_type_hints(cls).items() if not key.startswith("_")}
43
+
44
+ # delete_special_variables(annotations)
45
+ fields = []
46
+ for name, type_ in annotations.items():
47
+ if hasattr(type_, "__origin__") and type_.__origin__ is Column: # __origin__ to get type of Generic value
48
+ field_type = type_.__args__[0]
49
+ else:
50
+ # type_ must by Column object
51
+ field_type: TProp = type_
52
+
53
+ default: Column = getattr(cls, name, MISSING())
54
+
55
+ default.dtype = field_type # COMMENT: Useful for setting the dtype variable after instantiation.
56
+ fields.append(Field[TProp](name, field_type, default))
57
+
58
+ # Update __annotations__ to create Columns
59
+ cls.__annotations__[name] = default
60
+ return fields
@@ -1,75 +1,21 @@
1
+ from __future__ import annotations
2
+ from decimal import Decimal
3
+ from typing import Any, Optional, Type, dataclass_transform, overload, TYPE_CHECKING
1
4
  import base64
2
5
  import datetime
3
- from decimal import Decimal
4
- from typing import Any, Iterable, Optional, Type, dataclass_transform, get_type_hints
5
6
  import json
6
7
 
7
- from .dtypes import get_query_clausule
8
- from .module_tree.dfs_traversal import DFSTraversal
9
- from .column import Column
10
-
11
- from .foreign_key import ForeignKey, TableInfo
12
-
13
- MISSING = Column()
14
-
15
-
16
- class Field:
17
- def __init__(self, name: str, type_: Type, default: object) -> None:
18
- self.name: str = name
19
- self.type_: Type = type_
20
- self.default: Column = default
21
-
22
- def __repr__(self) -> str:
23
- return f"{Field.__name__}(name = {self.name}, type_ = {self.type_}, default = {self.default})"
24
-
25
- @property
26
- def has_default(self) -> bool:
27
- return self.default is not MISSING
8
+ import shapely as sph
28
9
 
29
- @property
30
- def init_arg(self) -> str:
31
- # default = f"={self.default_name if self.has_default else None}"
32
- default = f"={None}"
33
10
 
34
- return f"{self.name}: {self.type_name}{default}"
35
-
36
- @property
37
- def default_name(self) -> str:
38
- return f"_dflt_{self.name}"
39
-
40
- @property
41
- def type_name(self) -> str:
42
- return f"_type_{self.name}"
43
-
44
- @property
45
- def assginment(self) -> str:
46
- return f"self._{self.name} = {self.default.__to_string__(self.name,self.name,self.type_name)}"
47
-
48
-
49
- def delete_special_variables(dicc: dict[str, object]) -> None:
50
- keys = tuple(dicc.keys())
51
- for key in keys:
52
- if key.startswith("__"):
53
- del dicc[key]
54
-
55
-
56
- def get_fields[T](cls: Type[T]) -> Iterable[Field]:
57
- # COMMENT: Used the 'get_type_hints' method to resolve typing when 'from __future__ import annotations' is in use
58
- annotations = {key: val for key, val in get_type_hints(cls).items() if not key.startswith("_")}
59
-
60
- # delete_special_variables(annotations)
61
- fields = []
62
- for name, type_ in annotations.items():
63
- # type_ must by Column object
64
- field_type = type_
65
- if hasattr(type_, "__origin__") and type_.__origin__ is Column: # __origin__ to get type of Generic value
66
- field_type = type_.__args__[0]
67
- default: Column = getattr(cls, name, MISSING)
68
- fields.append(Field(name, field_type, default))
11
+ from .column import Column
12
+ from .dtypes import get_query_clausule
13
+ from .fields import get_fields
14
+ from .foreign_key import ForeignKey, TableInfo
15
+ from .module_tree.dfs_traversal import DFSTraversal
69
16
 
70
- # Update __annotations__ to create Columns
71
- cls.__annotations__[name] = Column[field_type]
72
- return fields
17
+ if TYPE_CHECKING:
18
+ from .fields import Field
73
19
 
74
20
 
75
21
  @dataclass_transform()
@@ -78,23 +24,32 @@ def __init_constructor__[T](cls: Type[T]) -> Type[T]:
78
24
  # TODOL: I don't know if it's better to create a global dictionary like in commit '7de69443d7a8e7264b8d5d604c95da0e5d7e9cc0'
79
25
  setattr(cls, "__properties_mapped__", {})
80
26
  fields = get_fields(cls)
81
- locals_ = {}
82
- init_args = []
27
+
28
+ locals_: dict[str, Any] = {}
29
+ init_args: list[str] = ["self"]
30
+ assignments: list[str] = []
83
31
 
84
32
  for field in fields:
85
- if not field.name.startswith("__"):
86
- locals_[field.type_name] = field.type_
33
+ if field.name.startswith("__"):
34
+ continue
35
+
36
+ locals_[field.type_name] = field.type_
37
+ locals_[field.default_name] = field.default.column_value
87
38
 
88
- init_args.append(field.init_arg)
89
- locals_[field.default_name] = None # field.default.column_value
90
- __create_properties(cls, field)
39
+ init_args.append(field.init_arg)
40
+ assignments.append(field.assginment)
41
+ __create_properties(cls, field)
42
+
43
+ string_locals_ = ", ".join(locals_)
44
+ string_init_args = ", ".join(init_args)
45
+ string_assignments = "\n\t\t".join(assignments)
91
46
 
92
47
  wrapper_fn = "\n".join(
93
48
  [
94
- f"def wrapper({', '.join(locals_.keys())}):",
95
- f" def __init__(self, {', '.join(init_args)}):",
96
- "\n".join([f" {f.assginment}" for f in fields]) or " pass",
97
- " return __init__",
49
+ f"def wrapper({string_locals_}):",
50
+ f"\n\tdef __init__({string_init_args}):",
51
+ f"\n\t\t{string_assignments}",
52
+ "\treturn __init__",
98
53
  ]
99
54
  )
100
55
 
@@ -109,11 +64,11 @@ def __init_constructor__[T](cls: Type[T]) -> Type[T]:
109
64
 
110
65
  def __create_properties(cls: Type["Table"], field: Field) -> property:
111
66
  _name: str = f"_{field.name}"
112
- type_ = field.type_
67
+
113
68
  # we need to get Table attributes (Column class) and then called __getattribute__ or __setattr__ to make changes inside of Column
114
69
  prop = property(
115
- fget=lambda self: __transform_getter(getattr(self, _name), type_),
116
- fset=lambda self, value: __transform_setter(getattr(self, _name), value, type_),
70
+ fget=lambda self: getattr(self, _name).__getattribute__("column_value"),
71
+ fset=lambda self, value: getattr(self, _name).__setattr__("column_value", value),
117
72
  )
118
73
 
119
74
  # set property in public name
@@ -122,22 +77,6 @@ def __create_properties(cls: Type["Table"], field: Field) -> property:
122
77
  return None
123
78
 
124
79
 
125
- def __transform_getter[T](obj: object, type_: T) -> T:
126
- return obj.__getattribute__("column_value")
127
-
128
- # if type_ is str and isinstance(eval(obj), Iterable):
129
- # getter = eval(obj)
130
- # return getter
131
-
132
-
133
- def __transform_setter[T](obj: object, value: Any, type_: T) -> None:
134
- return obj.__setattr__("column_value", value)
135
-
136
- # if type_ is list:
137
- # setter = str(setter)
138
- # return None
139
-
140
-
141
80
  class TableMeta(type):
142
81
  def __new__[T](cls: "Table", name: str, bases: tuple, dct: dict[str, Any]) -> Type[T]:
143
82
  """
@@ -244,6 +183,7 @@ class Table(metaclass=TableMeta):
244
183
  Decimal: str,
245
184
  bytes: byte_to_string,
246
185
  set: list,
186
+ sph.Point: lambda x: sph.to_wkt(x, rounding_precision=-1),
247
187
  }
248
188
 
249
189
  if (dtype := type(_value)) in transform_map:
@@ -324,3 +264,35 @@ class Table(metaclass=TableMeta):
324
264
  )
325
265
  )
326
266
  return False
267
+
268
+ @classmethod
269
+ def get_property_name(cls, _property: property) -> str:
270
+ name: str = cls.__properties_mapped__.get(_property, None)
271
+ if not name:
272
+ raise KeyError(f"Class '{cls.__name__}' has not propery '{_property}' mapped.")
273
+ return name
274
+
275
+ @overload
276
+ @classmethod
277
+ def get_column(cls, column: str) -> Column: ...
278
+ @overload
279
+ @classmethod
280
+ def get_column(cls, column: property) -> Column: ...
281
+ @overload
282
+ @classmethod
283
+ def get_column[TProp](cls, column: property, value: TProp) -> Column[TProp]: ...
284
+ @overload
285
+ @classmethod
286
+ def get_column[TProp](cls, column: str, value: TProp) -> Column[TProp]: ...
287
+ @classmethod
288
+ def get_column[TProp](cls, column: str | property, value: Optional[TProp] = None) -> Column[TProp]:
289
+ if isinstance(column, property):
290
+ _column = cls.get_property_name(column)
291
+ elif isinstance(column, str) and column in cls.get_columns():
292
+ _column = column
293
+ else:
294
+ raise ValueError(f"'Column' param with value'{column}' is not expected.")
295
+
296
+ instance_table: Table = cls(**{_column: value})
297
+
298
+ return getattr(instance_table, f"_{_column}")
@@ -1,14 +1,15 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ormlambda
3
- Version: 2.7.2
3
+ Version: 2.9.0
4
4
  Summary: ORM designed to interact with the database (currently with MySQL) using lambda functions and nested functions
5
5
  Author: p-hzamora
6
6
  Author-email: p.hzamora@icloud.com
7
7
  Requires-Python: >=3.12,<4.0
8
8
  Classifier: Programming Language :: Python :: 3
9
9
  Classifier: Programming Language :: Python :: 3.12
10
- Requires-Dist: fluent-validation (>=3.1.0,<4.0.0)
10
+ Requires-Dist: fluent-validation (==4.3.1)
11
11
  Requires-Dist: mysql-connector-python (>=9.0.0,<10.0.0)
12
+ Requires-Dist: shapely (>=2.0.6,<3.0.0)
12
13
  Description-Content-Type: text/markdown
13
14
 
14
15
  ![PyPI version](https://img.shields.io/pypi/v/ormlambda.svg)