ormlambda 3.34.0__py3-none-any.whl → 3.34.5__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 (60) hide show
  1. ormlambda/__init__.py +33 -0
  2. ormlambda/databases/__init__.py +4 -0
  3. ormlambda/databases/my_sql/__init__.py +3 -0
  4. ormlambda/{dialects/mysql → databases/my_sql}/caster/caster.py +5 -1
  5. ormlambda/{dialects/mysql → databases/my_sql}/caster/types/__init__.py +2 -0
  6. ormlambda/databases/my_sql/caster/types/date.py +34 -0
  7. ormlambda/databases/my_sql/caster/types/decimal.py +32 -0
  8. ormlambda/{dialects/mysql → databases/my_sql}/clauses/__init__.py +1 -0
  9. ormlambda/databases/my_sql/clauses/drop_table.py +26 -0
  10. ormlambda/{dialects/mysql/repository → databases/my_sql}/repository.py +5 -0
  11. ormlambda/dialects/mysql/__init__.py +3 -33
  12. ormlambda/dialects/mysql/base.py +194 -129
  13. ormlambda/dialects/mysql/types.py +4 -1
  14. ormlambda/engine/base.py +4 -23
  15. ormlambda/repository/interfaces/IRepositoryBase.py +7 -0
  16. ormlambda/sql/clause_info/clause_info.py +13 -0
  17. ormlambda/sql/column/column.py +26 -7
  18. ormlambda/sql/compiler.py +2 -243
  19. ormlambda/sql/ddl.py +4 -18
  20. ormlambda/sql/foreign_key.py +0 -18
  21. ormlambda/sql/sqltypes.py +12 -6
  22. ormlambda/sql/table/table.py +9 -5
  23. ormlambda/sql/type_api.py +3 -0
  24. ormlambda/sql/visitors.py +3 -0
  25. ormlambda/statements/interfaces/IStatements.py +0 -3
  26. ormlambda/statements/statements.py +1 -7
  27. ormlambda/util/__init__.py +2 -1
  28. ormlambda/util/load_module.py +21 -0
  29. ormlambda/util/module_tree/dynamic_module.py +1 -1
  30. {ormlambda-3.34.0.dist-info → ormlambda-3.34.5.dist-info}/METADATA +2 -3
  31. {ormlambda-3.34.0.dist-info → ormlambda-3.34.5.dist-info}/RECORD +59 -54
  32. {ormlambda-3.34.0.dist-info → ormlambda-3.34.5.dist-info}/WHEEL +1 -1
  33. ormlambda/dialects/mysql/repository/__init__.py +0 -1
  34. /ormlambda/{dialects/mysql → databases/my_sql}/caster/__init__.py +0 -0
  35. /ormlambda/{dialects/mysql → databases/my_sql}/caster/types/boolean.py +0 -0
  36. /ormlambda/{dialects/mysql → databases/my_sql}/caster/types/bytes.py +0 -0
  37. /ormlambda/{dialects/mysql → databases/my_sql}/caster/types/datetime.py +0 -0
  38. /ormlambda/{dialects/mysql → databases/my_sql}/caster/types/float.py +0 -0
  39. /ormlambda/{dialects/mysql → databases/my_sql}/caster/types/int.py +0 -0
  40. /ormlambda/{dialects/mysql → databases/my_sql}/caster/types/iterable.py +0 -0
  41. /ormlambda/{dialects/mysql → databases/my_sql}/caster/types/none.py +0 -0
  42. /ormlambda/{dialects/mysql → databases/my_sql}/caster/types/point.py +0 -0
  43. /ormlambda/{dialects/mysql → databases/my_sql}/caster/types/string.py +0 -0
  44. /ormlambda/{dialects/mysql → databases/my_sql}/clauses/ST_AsText.py +0 -0
  45. /ormlambda/{dialects/mysql → databases/my_sql}/clauses/ST_Contains.py +0 -0
  46. /ormlambda/{dialects/mysql → databases/my_sql}/clauses/count.py +0 -0
  47. /ormlambda/{dialects/mysql → databases/my_sql}/clauses/delete.py +0 -0
  48. /ormlambda/{dialects/mysql → databases/my_sql}/clauses/group_by.py +0 -0
  49. /ormlambda/{dialects/mysql → databases/my_sql}/clauses/having.py +0 -0
  50. /ormlambda/{dialects/mysql → databases/my_sql}/clauses/insert.py +0 -0
  51. /ormlambda/{dialects/mysql → databases/my_sql}/clauses/joins.py +0 -0
  52. /ormlambda/{dialects/mysql → databases/my_sql}/clauses/limit.py +0 -0
  53. /ormlambda/{dialects/mysql → databases/my_sql}/clauses/offset.py +0 -0
  54. /ormlambda/{dialects/mysql → databases/my_sql}/clauses/order.py +0 -0
  55. /ormlambda/{dialects/mysql → databases/my_sql}/clauses/update.py +0 -0
  56. /ormlambda/{dialects/mysql → databases/my_sql}/clauses/upsert.py +0 -0
  57. /ormlambda/{dialects/mysql → databases/my_sql}/clauses/where.py +0 -0
  58. /ormlambda/{dialects/mysql/repository → databases/my_sql}/pool_types.py +0 -0
  59. {ormlambda-3.34.0.dist-info → ormlambda-3.34.5.dist-info}/AUTHORS +0 -0
  60. {ormlambda-3.34.0.dist-info → ormlambda-3.34.5.dist-info}/LICENSE +0 -0
@@ -32,7 +32,7 @@ class _NumericType(_NumericCommonType, sqltypes.Numeric): ...
32
32
  class _FloatType(_NumericCommonType, sqltypes.Float):
33
33
  def __init__(self, precision=None, scale=None, asdecimal=True, **kw):
34
34
  if isinstance(self, (REAL, DOUBLE)) and ((precision is None and scale is not None) or (precision is not None and scale is None)):
35
- raise AttributeError("You must specify both precision and scale or omit " "both altogether.")
35
+ raise AttributeError("You must specify both precision and scale or omit both altogether.")
36
36
  super().__init__(precision=precision, asdecimal=asdecimal, **kw)
37
37
  self.scale = scale
38
38
 
@@ -42,6 +42,9 @@ class _IntegerType(_NumericCommonType, sqltypes.Integer):
42
42
  self.display_width = display_width
43
43
  super().__init__(**kw)
44
44
 
45
+ def __repr__(self):
46
+ return f"{type(self).__name__}({self.display_width})"
47
+
45
48
 
46
49
  class _StringType(sqltypes.String):
47
50
  """Base for MySQL string types."""
ormlambda/engine/base.py CHANGED
@@ -1,8 +1,8 @@
1
1
  from __future__ import annotations
2
- from pathlib import Path
3
- from typing import TYPE_CHECKING, BinaryIO, Literal, Optional, TextIO
2
+ from typing import TYPE_CHECKING, Literal
4
3
  from ormlambda.engine import url
5
- from ormlambda.sql.ddl import CreateSchema, DropSchema, CreateBackup
4
+ from ormlambda.sql.ddl import CreateSchema, DropSchema
5
+
6
6
 
7
7
  if TYPE_CHECKING:
8
8
  from ormlambda.dialects import Dialect
@@ -20,7 +20,7 @@ class Engine:
20
20
  if if_exists == "replace":
21
21
  self.drop_schema(schema_name, if_exists)
22
22
 
23
- sql = CreateSchema(schema=schema_name, if_not_exists=if_exists == "append").compile(self.dialect).string
23
+ sql = CreateSchema(schema=schema_name, if_not_exists=if_exists== 'append').compile(self.dialect).string
24
24
  try:
25
25
  self.repository.execute(sql)
26
26
  except Exception:
@@ -56,22 +56,3 @@ class Engine:
56
56
  def set_database(self, name: str) -> None:
57
57
  self.repository.database = name
58
58
  return None
59
-
60
- def create_backup(
61
- self,
62
- output: Optional[str | BinaryIO | TextIO] = None,
63
- compress: bool = False,
64
- backup_dir: str = "backups",
65
- **kw,
66
- ) -> Optional[str | BinaryIO | Path]:
67
- return (
68
- CreateBackup(self.url)
69
- .compile(
70
- self.dialect,
71
- output=output,
72
- compress=compress,
73
- backup_dir=backup_dir,
74
- **kw,
75
- )
76
- .string
77
- )
@@ -9,8 +9,12 @@ from typing import (
9
9
  Sequence,
10
10
  Type,
11
11
  Iterable,
12
+ TYPE_CHECKING,
12
13
  )
13
14
 
15
+ if TYPE_CHECKING:
16
+ from ormlambda.statements.types import TypeExists
17
+
14
18
 
15
19
  type _DBAPICursorDescription = Sequence[
16
20
  tuple[
@@ -138,6 +142,9 @@ class IRepositoryBase(ABC):
138
142
  @abstractmethod
139
143
  def execute(self, query: str) -> None: ...
140
144
 
145
+ @abstractmethod
146
+ def drop_table(self, name: str) -> None: ...
147
+
141
148
  @abstractmethod
142
149
  def table_exists(self, name: str) -> bool: ...
143
150
 
@@ -9,6 +9,7 @@ from ormlambda.sql.types import (
9
9
  TableType,
10
10
  ColumnType,
11
11
  AliasType,
12
+ TypeEngine,
12
13
  )
13
14
  from .interface import IClauseInfo
14
15
  from ormlambda.sql import ForeignKey
@@ -166,6 +167,18 @@ class ClauseInfo[T: Table](IClauseInfo[T]):
166
167
  return self._column
167
168
  return type(self._column)
168
169
 
170
+ @property
171
+ def dbtype(self)->tp.Optional[TypeEngine]:
172
+ if self._dtype is not None:
173
+ return self._dtype
174
+
175
+ if isinstance(self._column, Column):
176
+ return self._column.dbtype
177
+
178
+ if isinstance(self._column, type):
179
+ return self._column
180
+ return type(self._column)
181
+
169
182
  def query(self, dialect: Dialect, **kwargs) -> str:
170
183
  return self._create_query(dialect, **kwargs)
171
184
 
@@ -1,14 +1,14 @@
1
1
  from __future__ import annotations
2
2
  import abc
3
- from typing import Annotated, Any, Iterable, Type, Optional, TYPE_CHECKING, get_type_hints, overload, get_origin, get_args
3
+ from typing import Annotated, Any, ClassVar, Iterable, Type, Optional, TYPE_CHECKING, get_type_hints, overload, get_origin, get_args
4
4
  from ormlambda.sql.types import TableType, ComparerType, ColumnType
5
5
  from ormlambda import ConditionType
6
+ from ormlambda.sql.type_api import TypeEngine
6
7
 
7
8
  if TYPE_CHECKING:
8
9
  import re
9
10
  from ormlambda import Table
10
11
  from ormlambda.sql.comparer import Comparer, Regex, Like
11
- from ormlambda.sql.type_api import TypeEngine
12
12
 
13
13
 
14
14
  from ormlambda.types import (
@@ -24,10 +24,13 @@ from ormlambda.types import (
24
24
 
25
25
 
26
26
  class Column[TProp]:
27
- PRIVATE_CHAR: str = "_"
27
+ PRIVATE_CHAR: ClassVar[str] = "_"
28
+
29
+ _dbtype: Optional[TypeEngine]
28
30
 
29
31
  __slots__ = (
30
- "dtype",
32
+ "_dtype",
33
+ "_dbtype",
31
34
  "column_name",
32
35
  "table",
33
36
  "is_primary_key",
@@ -111,7 +114,8 @@ class Column[TProp]:
111
114
 
112
115
  def __set__(self, obj, value):
113
116
  if self._check and value is not None:
114
- if not isinstance(value, self.dtype):
117
+ type_ = self.dtype if not isinstance(self.dtype, TypeEngine) else self.dtype.python_type
118
+ if not isinstance(value, type_):
115
119
  raise ValueError(f"The '{self.column_name}' Column from '{self.table.__table_name__}' table expected '{str(self.dtype)}' type. You passed '{type(value).__name__}' type")
116
120
  setattr(obj, self.__private_name, value)
117
121
 
@@ -132,8 +136,6 @@ class Column[TProp]:
132
136
  def _fill_from_annotations[T: Table](self, obj: Type[T], name: str) -> None:
133
137
  """Read the metada when using Annotated typing class, and set the attributes accordingly"""
134
138
 
135
- from ormlambda.sql.type_api import TypeEngine
136
-
137
139
  annotations = get_type_hints(obj, include_extras=True)
138
140
  if name in annotations:
139
141
  annotation = annotations[name]
@@ -163,6 +165,23 @@ class Column[TProp]:
163
165
  self.is_not_null = True
164
166
  return None
165
167
 
168
+ @property
169
+ def dtype(self) -> Type[TProp]:
170
+ return self._dtype
171
+
172
+ @dtype.setter
173
+ def dtype(self, value: Type[TProp] | TypeEngine) -> None:
174
+ if isinstance(value, TypeEngine):
175
+ self._dtype = value.python_type
176
+ self._dbtype = value
177
+ else:
178
+ self._dtype = value
179
+ self._dbtype = None
180
+
181
+ @property
182
+ def dbtype(self) -> TypeEngine[TProp]:
183
+ return self._dbtype if self._dbtype else self.dtype
184
+
166
185
  @abc.abstractmethod
167
186
  def __comparer_creator(self, other: ColumnType, compare: ComparerType) -> Comparer:
168
187
  from ormlambda.sql.comparer import Comparer
ormlambda/sql/compiler.py CHANGED
@@ -1,13 +1,6 @@
1
1
  from __future__ import annotations
2
2
  import abc
3
- import datetime
4
- import io
5
- import logging
6
- import os
7
- from pathlib import Path
8
- import subprocess
9
- import sys
10
- from typing import Any, BinaryIO, ClassVar, Optional, TYPE_CHECKING, TextIO, Union
3
+ from typing import Any, ClassVar, Optional, TYPE_CHECKING
11
4
 
12
5
  from ormlambda.sql.ddl import CreateColumn
13
6
  from ormlambda.sql.foreign_key import ForeignKey
@@ -22,13 +15,7 @@ if TYPE_CHECKING:
22
15
  from .visitors import Element
23
16
  from .elements import ClauseElement
24
17
  from ormlambda.dialects import Dialect
25
- from ormlambda.sql.ddl import (
26
- CreateTable,
27
- CreateSchema,
28
- DropSchema,
29
- DropTable,
30
- CreateBackup,
31
- )
18
+ from ormlambda.sql.ddl import CreateTable, CreateSchema, DropSchema
32
19
  from .sqltypes import (
33
20
  INTEGER,
34
21
  SMALLINTEGER,
@@ -75,12 +62,6 @@ if TYPE_CHECKING:
75
62
  )
76
63
 
77
64
 
78
- type customString = Union[str | Path]
79
-
80
- logging.basicConfig(stream=sys.stdout, level=logging.INFO)
81
- log = logging.getLogger(__name__)
82
-
83
-
84
65
  class Compiled:
85
66
  """Represent a compiled SQL or DDL expression.
86
67
 
@@ -268,9 +249,6 @@ class DDLCompiler(Compiled):
268
249
  sql += f"\n){table_options};"
269
250
  return sql
270
251
 
271
- def visit_drop_table(self, drop: DropTable, **kw) -> str:
272
- return "DROP TABLE " + drop.element.__table_name__
273
-
274
252
  def visit_create_column(self, create: CreateColumn, first_pk=False, **kw): # noqa: F821
275
253
  column = create.element
276
254
  return self.get_column_specification(column)
@@ -295,225 +273,6 @@ class DDLCompiler(Compiled):
295
273
  return None
296
274
  return None
297
275
 
298
- #TODOH []: refactor in order to improve clarity
299
- def visit_create_backup(
300
- self,
301
- backup: CreateBackup,
302
- output: Optional[Union[Path | str, BinaryIO, TextIO]] = None,
303
- compress: bool = False,
304
- backup_dir: customString = ".",
305
- **kw,
306
- ) -> Optional[str | BinaryIO | Path]:
307
- """
308
- Create MySQL backup with flexible output options
309
-
310
- Args:
311
- backup: An object containing database connection details (host, user, password, database, port).
312
- output: Output destination:
313
- - None: Auto-generate file path
314
- - str: Custom file path (treated as a path-like object)
315
- - Stream object: Write to stream (io.StringIO, io.BytesIO, sys.stdout, etc.)
316
- compress: Whether to compress the output using gzip.
317
- backup_dir: Directory for auto-generated files if 'output' is None.
318
-
319
- Returns:
320
- - File path (str) if output to file.
321
- - Backup data as bytes (if output to binary stream) or string (if output to text stream).
322
- - None if an error occurs.
323
- """
324
-
325
- host = backup.url.host
326
- user = backup.url.username
327
- password = backup.url.password
328
- database = backup.url.database
329
- port = backup.url.port
330
-
331
- if not database:
332
- log.error("Error: Database name is required for backup.")
333
- return None
334
-
335
- # Build mysqldump command
336
- command = [
337
- "mysqldump",
338
- f"--host={host}",
339
- f"--port={port}",
340
- f"--user={user}",
341
- f"--password={password}",
342
- "--single-transaction",
343
- "--routines",
344
- "--triggers",
345
- "--events",
346
- "--lock-tables=false", # Often used to avoid locking during backup
347
- "--add-drop-table",
348
- "--extended-insert",
349
- database,
350
- ]
351
-
352
- def export_to_stream_internal() -> Optional[io.BytesIO]:
353
- nonlocal command, compress, database
354
- # If streaming, execute mysqldump and capture stdout
355
- log.info(f"Backing up database '{database}' to BytesIO...")
356
-
357
- try:
358
- if compress:
359
- # Start mysqldump process
360
- mysqldump_process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
361
-
362
- # Start gzip process, taking input from mysqldump
363
- gzip_process = subprocess.Popen(["gzip", "-c"], stdin=mysqldump_process.stdout, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
364
-
365
- # Close mysqldump stdout in parent process - gzip will handle it
366
- mysqldump_process.stdout.close()
367
-
368
- # Wait for gzip to complete (which will also wait for mysqldump)
369
- gzip_stdout, gzip_stderr = gzip_process.communicate()
370
-
371
- # Wait for mysqldump to finish and get its stderr
372
- mysqldump_stderr = mysqldump_process.communicate()[1]
373
-
374
- if mysqldump_process.returncode != 0:
375
- log.error(f"mysqldump error: {mysqldump_stderr.decode().strip()}")
376
- return None
377
- if gzip_process.returncode != 0:
378
- log.error(f"gzip error: {gzip_stderr.decode().strip()}")
379
- return None
380
-
381
- log.info("Backup successful and compressed to BytesIO.")
382
- return io.BytesIO(gzip_stdout)
383
- else:
384
- # Directly capture mysqldump output
385
- process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
386
- stdout, stderr = process.communicate()
387
-
388
- if process.returncode != 0:
389
- log.error(f"mysqldump error: {stderr.decode().strip()}")
390
- return None
391
-
392
- log.info("Backup successful to BytesIO.")
393
- return io.BytesIO(stdout)
394
-
395
- except FileNotFoundError as e:
396
- log.error(f"Error: '{e.filename}' command not found. Please ensure mysqldump (and gzip if compressing) is installed and in your system's PATH.")
397
- return None
398
- except Exception as e:
399
- log.error(f"An unexpected error occurred during streaming backup: {e}")
400
- return None
401
-
402
- def export_to_file_internal(file_path: customString) -> Optional[Path]:
403
- nonlocal command, compress, database
404
-
405
- if isinstance(file_path, str):
406
- file_path = Path(file_path)
407
-
408
- if not file_path.exists():
409
- file_path.parent.mkdir(parents=True, exist_ok=True)
410
- file_path.touch()
411
-
412
- try:
413
- if compress:
414
- # Pipe mysqldump output through gzip to file
415
- with open(file_path, "wb") as output_file:
416
- # Start mysqldump process
417
- mysqldump_process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
418
-
419
- # Start gzip process, taking input from mysqldump and writing to file
420
- gzip_process = subprocess.Popen(["gzip", "-c"], stdin=mysqldump_process.stdout, stdout=output_file, stderr=subprocess.PIPE)
421
-
422
- # Close mysqldump stdout in parent process - gzip will handle it
423
- mysqldump_process.stdout.close()
424
-
425
- # Wait for gzip to complete (which will also wait for mysqldump)
426
- gzip_stdout, gzip_stderr = gzip_process.communicate()
427
-
428
- # Wait for mysqldump to finish and get its stderr
429
- mysqldump_stderr = mysqldump_process.communicate()[1]
430
-
431
- if mysqldump_process.returncode != 0:
432
- log.error(f"mysqldump error: {mysqldump_stderr.decode().strip()}")
433
- return None
434
- if gzip_process.returncode != 0:
435
- log.error(f"gzip error: {gzip_stderr.decode().strip()}")
436
- return None
437
- else:
438
- # Directly redirect mysqldump output to file
439
- with open(file_path, "wb") as output_file:
440
- process = subprocess.Popen(command, stdout=output_file, stderr=subprocess.PIPE)
441
- stdout, stderr = process.communicate()
442
-
443
- if process.returncode != 0:
444
- log.error(f"mysqldump error: {stderr.decode().strip()}")
445
- return None
446
-
447
- log.info(f"Backup completed successfully: {file_path}")
448
- return file_path
449
-
450
- except FileNotFoundError as e:
451
- log.error(f"Error: '{e.filename}' command not found. Please ensure mysqldump (and gzip if compressing) is installed and in your system's PATH.")
452
- return None
453
- except Exception as e:
454
- log.error(f"An unexpected error occurred during file backup: {e}")
455
- return None
456
-
457
- try:
458
- if output is None:
459
- # Auto-generate file path
460
-
461
- backup_dir = Path(backup_dir)
462
- backup_dir.mkdir(exist_ok=True)
463
-
464
- timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
465
- file_extension = "sql.gz" if compress else "sql"
466
- output_filename = f"{database}_backup_{timestamp}.{file_extension}"
467
- output_filepath = os.path.join(backup_dir, output_filename)
468
- return export_to_file_internal(output_filepath)
469
-
470
- elif isinstance(output, (io.BytesIO, io.StringIO)):
471
- # Output to a stream object
472
- stream_result = export_to_stream_internal()
473
- if stream_result:
474
- # Write the content from the internal BytesIO to the provided output stream
475
- if isinstance(output, io.BytesIO):
476
- output.write(stream_result.getvalue())
477
- return stream_result.getvalue() # Return bytes if it was a BytesIO internally
478
- elif isinstance(output, io.StringIO):
479
- # Attempt to decode bytes to string if target is StringIO
480
- try:
481
- decoded_content = stream_result.getvalue().decode("utf-8")
482
- output.write(decoded_content)
483
- return decoded_content
484
- except UnicodeDecodeError:
485
- log.error("Error: Cannot decode byte stream to UTF-8 for StringIO output. Consider setting compress=False or ensuring database encoding is compatible.")
486
- return None
487
- return None
488
-
489
- elif isinstance(output, str | Path):
490
- # Output to a custom file path
491
- return export_to_file_internal(output)
492
-
493
- elif isinstance(output, (BinaryIO, TextIO)): # Handles sys.stdout, open file objects
494
- stream_result = export_to_stream_internal()
495
- if stream_result:
496
- if "b" in getattr(output, "mode", "") or isinstance(output, BinaryIO): # Check if it's a binary stream
497
- output.write(stream_result.getvalue())
498
- return stream_result.getvalue()
499
- else: # Assume text stream
500
- try:
501
- decoded_content = stream_result.getvalue().decode("utf-8")
502
- output.write(decoded_content)
503
- return decoded_content
504
- except UnicodeDecodeError:
505
- log.error("Error: Cannot decode byte stream to UTF-8 for text stream output. Consider setting compress=False or ensuring database encoding is compatible.")
506
- return None
507
- return None
508
-
509
- else:
510
- log.error(f"Unsupported output type: {type(output)}")
511
- return None
512
-
513
- except Exception as e:
514
- log.error(f"An unexpected error occurred: {e}")
515
- return None
516
-
517
276
 
518
277
  class GenericTypeCompiler(TypeCompiler):
519
278
  """Generic type compiler
ormlambda/sql/ddl.py CHANGED
@@ -3,7 +3,6 @@ from typing import TYPE_CHECKING
3
3
  from .elements import ClauseElement
4
4
 
5
5
  if TYPE_CHECKING:
6
- from ormlambda import URL
7
6
  from ormlambda.dialects.interface.dialect import Dialect
8
7
  from ormlambda import Column
9
8
  from ormlambda import Table
@@ -23,22 +22,16 @@ class BaseDDLElement(ClauseElement):
23
22
  return dialect.ddl_compiler(dialect, self, **kw)
24
23
 
25
24
 
26
- class CreateDropTable:
27
- def __init__(self, element: Table):
28
- self.element = element
29
- self.columns = [CreateColumn(c) for c in element.get_columns()]
30
-
31
-
32
- class CreateTable(CreateDropTable, BaseDDLElement):
25
+ class CreateTable(BaseDDLElement):
33
26
  """
34
27
  Class representing a CREATE TABLE statement.
35
28
  """
36
29
 
37
30
  __visit_name__ = "create_table"
38
31
 
39
-
40
- class DropTable(CreateDropTable, BaseDDLElement):
41
- __visit_name__ = "drop_table"
32
+ def __init__(self, element: Table):
33
+ self.element = element
34
+ self.columns = [CreateColumn(c) for c in element.get_columns()]
42
35
 
43
36
 
44
37
  class CreateColumn[T](BaseDDLElement):
@@ -73,10 +66,3 @@ class SchemaExists(BaseDDLElement):
73
66
 
74
67
  def __init__(self, schema: str):
75
68
  self.schema = schema
76
-
77
-
78
- class CreateBackup(BaseDDLElement):
79
- __visit_name__ = "create_backup"
80
-
81
- def __init__(self, url: URL):
82
- self.url = url
@@ -36,14 +36,6 @@ class ForeignKeyContext(set["ForeignKey"]):
36
36
  class ForeignKey[TLeft: Table, TRight: Table](Element, IQuery):
37
37
  __visit_name__ = "foreign_key"
38
38
 
39
- __slots__ = (
40
- "_tright",
41
- "_relationship",
42
- "_comparer",
43
- "_clause_name",
44
- "_keep_alive",
45
- )
46
-
47
39
  stored_calls: ClassVar[ForeignKeyContext] = ForeignKeyContext()
48
40
 
49
41
  @overload
@@ -157,13 +149,3 @@ class ForeignKey[TLeft: Table, TRight: Table](Element, IQuery):
157
149
  comparer.set_context(context)
158
150
  comparer._dialect = dialect
159
151
  return comparer
160
-
161
- def __hash__(self):
162
- return hash((
163
- self._tleft,
164
- self._tright,
165
- self._clause_name,
166
- ))
167
-
168
- def __eq__(self, other: ForeignKey):
169
- return hash(other) == hash(self)
ormlambda/sql/sqltypes.py CHANGED
@@ -10,6 +10,7 @@ import ormlambda.util as util
10
10
  from uuid import UUID as _python_UUID
11
11
  from shapely import Point as _python_Point
12
12
 
13
+
13
14
  class _NoArg(enum.Enum):
14
15
  NO_ARG = 0
15
16
 
@@ -391,7 +392,7 @@ class Enum(String, TypeEngine[EnumType]):
391
392
  enum_args = get_args(pt)
392
393
  bad_args = [arg for arg in enum_args if not isinstance(arg, str)]
393
394
  if bad_args:
394
- raise ValueError(f"Can't create string-based Enum datatype from non-string " f"values: {', '.join(repr(x) for x in bad_args)}. Please " f"provide an explicit Enum datatype for this Python type")
395
+ raise ValueError(f"Can't create string-based Enum datatype from non-string values: {', '.join(repr(x) for x in bad_args)}. Please provide an explicit Enum datatype for this Python type")
395
396
  native_enum = False
396
397
  return enum_args, native_enum
397
398
 
@@ -407,7 +408,7 @@ class Enum(String, TypeEngine[EnumType]):
407
408
  elif util.is_pep695(python_type):
408
409
  value = python_type.__value__
409
410
  if not util.is_literal(value):
410
- raise ValueError(f"Can't associate TypeAliasType '{python_type}' to an " "Enum since it's not a direct alias of a Literal. Only " "aliases in this form `type my_alias = Literal['a', " "'b']` are supported when generating Enums.")
411
+ raise ValueError(f"Can't associate TypeAliasType '{python_type}' to an Enum since it's not a direct alias of a Literal. Only aliases in this form `type my_alias = Literal['a', 'b']` are supported when generating Enums.")
411
412
  enum_args, native_enum = process_literal(value)
412
413
 
413
414
  elif isinstance(python_type, type) and issubclass(python_type, enum.Enum):
@@ -439,13 +440,15 @@ class Enum(String, TypeEngine[EnumType]):
439
440
 
440
441
  self._valid_lookup.update([(value, self._valid_lookup[self._object_lookup[value]]) for value in values])
441
442
 
443
+
442
444
  class Point(TypeEngine[_python_Point]):
443
- __visit_name__ = 'point'
445
+ __visit_name__ = "point"
444
446
 
445
447
  @property
446
- def python_type(self)->Type[_python_Point]:
448
+ def python_type(self) -> Type[_python_Point]:
447
449
  return _python_Point
448
-
450
+
451
+
449
452
  class JSON(TypeEngine[Any]):
450
453
  """JSON data type."""
451
454
 
@@ -597,6 +600,7 @@ class VARBINARY(Varbinary):
597
600
  class ENUM(Enum):
598
601
  __visit_name__ = "ENUM"
599
602
 
603
+
600
604
  class POINT(Point):
601
605
  __visit_name__ = "POINT"
602
606
 
@@ -623,13 +627,15 @@ _type_dicc: dict[Any, TypeEngine[Any]] = {
623
627
  float: Float(),
624
628
  NoneType: NULLTYPE,
625
629
  dt.datetime: Datetime(timezone=False),
630
+ dt.date: DATE(),
626
631
  bytes: _BINARY,
627
632
  bytearray: _BINARY,
628
633
  bool: Boolean(),
629
634
  enum.Enum: _ENUM,
630
635
  Literal: _ENUM,
631
636
  _python_UUID: UUID(),
632
- _python_Point: POINT()
637
+ _python_Point: POINT(),
638
+ decimal.Decimal: DECIMAL(),
633
639
  }
634
640
  # enderegion
635
641
 
@@ -5,9 +5,10 @@ import json
5
5
  from ormlambda.sql import Column
6
6
  from ormlambda.sql import ForeignKey
7
7
  from ormlambda.util.module_tree.dfs_traversal import DFSTraversal
8
- from ormlambda.sql.ddl import CreateTable, DropTable
8
+ from ormlambda.sql.ddl import CreateTable
9
9
 
10
10
  if TYPE_CHECKING:
11
+ from ormlambda.statements import BaseStatement
11
12
  from ormlambda.dialects import Dialect
12
13
 
13
14
  from .table_constructor import __init_constructor__
@@ -114,14 +115,17 @@ class Table(metaclass=TableMeta):
114
115
  if name == key:
115
116
  return value
116
117
 
118
+ @classmethod
119
+ def create_table_query(cls, statement: BaseStatement) -> str:
120
+ """It's classmethod because of it does not matter the columns values to create the table"""
121
+ from ormlambda.sql.schema_generator import SchemaGeneratorFactory
122
+
123
+ return SchemaGeneratorFactory.get_generator(statement._dialect).create_table(cls)
124
+
117
125
  @classmethod
118
126
  def create_table(cls, dialect: Dialect) -> str:
119
127
  return CreateTable(cls).compile(dialect).string
120
128
 
121
- @classmethod
122
- def drop_table(cls, dialect:Dialect)->str:
123
- return DropTable(cls).compile(dialect).string
124
-
125
129
  @classmethod
126
130
  def find_dependent_tables(cls) -> tuple["Table", ...]:
127
131
  """Work in progress"""
ormlambda/sql/type_api.py CHANGED
@@ -33,3 +33,6 @@ class TypeEngine[T: Any](Element, abc.ABC):
33
33
  if _coerced_type is NULLTYPE:
34
34
  return self
35
35
  return _coerced_type
36
+
37
+ def __repr__(self):
38
+ return f"{TypeEngine.__name__}: {super().__repr__()}"
ormlambda/sql/visitors.py CHANGED
@@ -72,3 +72,6 @@ class Element:
72
72
  # return visitor.visit_unsupported_compilation(self, err, **kw) # type: ignore # noqa: E501
73
73
 
74
74
  cls._compiler_dispatch = _compiler_dispatch
75
+
76
+ def __repr__(self):
77
+ return f"{Element.__name__}: {self.__visit_name__}"
@@ -37,9 +37,6 @@ class IStatements[T: Table](Element):
37
37
  @abstractmethod
38
38
  def create_table(self, if_exists: TypeExists = "fail") -> None: ...
39
39
 
40
- @abstractmethod
41
- def drop_table(self) -> None: ...
42
-
43
40
  # #TODOL [ ]: We must to implement this mehtod
44
41
  # @abstractmethod
45
42
  # def drop_table(self)->None: ...