iceaxe 0.8.3__cp313-cp313-macosx_11_0_arm64.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.

Potentially problematic release.


This version of iceaxe might be problematic. Click here for more details.

Files changed (75) hide show
  1. iceaxe/__init__.py +20 -0
  2. iceaxe/__tests__/__init__.py +0 -0
  3. iceaxe/__tests__/benchmarks/__init__.py +0 -0
  4. iceaxe/__tests__/benchmarks/test_bulk_insert.py +45 -0
  5. iceaxe/__tests__/benchmarks/test_select.py +114 -0
  6. iceaxe/__tests__/conf_models.py +133 -0
  7. iceaxe/__tests__/conftest.py +204 -0
  8. iceaxe/__tests__/docker_helpers.py +208 -0
  9. iceaxe/__tests__/helpers.py +268 -0
  10. iceaxe/__tests__/migrations/__init__.py +0 -0
  11. iceaxe/__tests__/migrations/conftest.py +36 -0
  12. iceaxe/__tests__/migrations/test_action_sorter.py +237 -0
  13. iceaxe/__tests__/migrations/test_generator.py +140 -0
  14. iceaxe/__tests__/migrations/test_generics.py +91 -0
  15. iceaxe/__tests__/mountaineer/__init__.py +0 -0
  16. iceaxe/__tests__/mountaineer/dependencies/__init__.py +0 -0
  17. iceaxe/__tests__/mountaineer/dependencies/test_core.py +76 -0
  18. iceaxe/__tests__/schemas/__init__.py +0 -0
  19. iceaxe/__tests__/schemas/test_actions.py +1265 -0
  20. iceaxe/__tests__/schemas/test_cli.py +25 -0
  21. iceaxe/__tests__/schemas/test_db_memory_serializer.py +1571 -0
  22. iceaxe/__tests__/schemas/test_db_serializer.py +435 -0
  23. iceaxe/__tests__/schemas/test_db_stubs.py +190 -0
  24. iceaxe/__tests__/test_alias.py +83 -0
  25. iceaxe/__tests__/test_base.py +52 -0
  26. iceaxe/__tests__/test_comparison.py +383 -0
  27. iceaxe/__tests__/test_field.py +11 -0
  28. iceaxe/__tests__/test_helpers.py +9 -0
  29. iceaxe/__tests__/test_modifications.py +151 -0
  30. iceaxe/__tests__/test_queries.py +764 -0
  31. iceaxe/__tests__/test_queries_str.py +173 -0
  32. iceaxe/__tests__/test_session.py +1511 -0
  33. iceaxe/__tests__/test_text_search.py +287 -0
  34. iceaxe/alias_values.py +67 -0
  35. iceaxe/base.py +351 -0
  36. iceaxe/comparison.py +560 -0
  37. iceaxe/field.py +263 -0
  38. iceaxe/functions.py +1432 -0
  39. iceaxe/generics.py +140 -0
  40. iceaxe/io.py +107 -0
  41. iceaxe/logging.py +91 -0
  42. iceaxe/migrations/__init__.py +5 -0
  43. iceaxe/migrations/action_sorter.py +98 -0
  44. iceaxe/migrations/cli.py +228 -0
  45. iceaxe/migrations/client_io.py +62 -0
  46. iceaxe/migrations/generator.py +404 -0
  47. iceaxe/migrations/migration.py +86 -0
  48. iceaxe/migrations/migrator.py +101 -0
  49. iceaxe/modifications.py +176 -0
  50. iceaxe/mountaineer/__init__.py +10 -0
  51. iceaxe/mountaineer/cli.py +74 -0
  52. iceaxe/mountaineer/config.py +46 -0
  53. iceaxe/mountaineer/dependencies/__init__.py +6 -0
  54. iceaxe/mountaineer/dependencies/core.py +67 -0
  55. iceaxe/postgres.py +133 -0
  56. iceaxe/py.typed +0 -0
  57. iceaxe/queries.py +1459 -0
  58. iceaxe/queries_str.py +294 -0
  59. iceaxe/schemas/__init__.py +0 -0
  60. iceaxe/schemas/actions.py +864 -0
  61. iceaxe/schemas/cli.py +30 -0
  62. iceaxe/schemas/db_memory_serializer.py +711 -0
  63. iceaxe/schemas/db_serializer.py +347 -0
  64. iceaxe/schemas/db_stubs.py +529 -0
  65. iceaxe/session.py +860 -0
  66. iceaxe/session_optimized.c +12207 -0
  67. iceaxe/session_optimized.cpython-313-darwin.so +0 -0
  68. iceaxe/session_optimized.pyx +212 -0
  69. iceaxe/sql_types.py +149 -0
  70. iceaxe/typing.py +73 -0
  71. iceaxe-0.8.3.dist-info/METADATA +262 -0
  72. iceaxe-0.8.3.dist-info/RECORD +75 -0
  73. iceaxe-0.8.3.dist-info/WHEEL +6 -0
  74. iceaxe-0.8.3.dist-info/licenses/LICENSE +21 -0
  75. iceaxe-0.8.3.dist-info/top_level.txt +1 -0
iceaxe/queries_str.py ADDED
@@ -0,0 +1,294 @@
1
+ from __future__ import annotations
2
+
3
+ from abc import ABC, abstractmethod
4
+
5
+ from iceaxe.typing import is_base_table, is_column
6
+
7
+
8
+ class QueryElementBase(ABC):
9
+ """
10
+ Abstract base class for SQL query elements that require special string processing.
11
+ This class provides the foundation for handling different types of SQL elements
12
+ (like identifiers and literals) with their specific escaping and formatting rules.
13
+
14
+ The class implements equality comparisons, hashing, and sorting based on the processed
15
+ string representation, making it suitable for comparing query elements in test assertions,
16
+ caching, using elements as dictionary keys or in sets, and sorting collections.
17
+
18
+ ```python {{sticky: True}}
19
+ # Base class is not used directly, but through its subclasses:
20
+ table_name = QueryIdentifier("users") # -> "users"
21
+ raw_sql = QueryLiteral("COUNT(*)") # -> COUNT(*)
22
+ ```
23
+ """
24
+
25
+ def __init__(self, value: str):
26
+ """
27
+ :param value: The raw string value to be processed
28
+ """
29
+ self._value = self.process_value(value)
30
+
31
+ @abstractmethod
32
+ def process_value(self, value: str) -> str:
33
+ """
34
+ Process the input value according to the specific rules of the query element type.
35
+ Must be implemented by subclasses.
36
+
37
+ :param value: The raw string value to process
38
+ :return: The processed string value
39
+ """
40
+ pass
41
+
42
+ def __eq__(self, compare):
43
+ return str(self) == str(compare)
44
+
45
+ def __ne__(self, compare):
46
+ return str(self) != str(compare)
47
+
48
+ def __lt__(self, other):
49
+ """
50
+ Enable sorting of query elements based on their string representation.
51
+ This makes QueryElementBase instances sortable using sorted() or list.sort().
52
+
53
+ :param other: Another QueryElementBase instance to compare with
54
+ :return: True if this element's string representation comes before the other's
55
+ """
56
+ return str(self) < str(other)
57
+
58
+ def __str__(self):
59
+ return self._value
60
+
61
+ def __repr__(self):
62
+ return f"{self.__class__.__name__}({self._value})"
63
+
64
+ def __hash__(self):
65
+ """
66
+ Generate a hash based on the string representation of the query element.
67
+ This makes QueryElementBase instances usable as dictionary keys and in sets.
68
+
69
+ :return: Hash value of the processed string
70
+ """
71
+ return hash(str(self))
72
+
73
+
74
+ class QueryIdentifier(QueryElementBase):
75
+ """
76
+ Represents a SQL identifier (table name, column name, etc.) that needs to be
77
+ properly quoted to prevent SQL injection and handle special characters.
78
+
79
+ When used, the identifier is automatically wrapped in double quotes, making it
80
+ safe for use in queries even if it contains special characters or SQL keywords.
81
+
82
+ ```python {{sticky: True}}
83
+ # In a query builder context:
84
+ table = QueryIdentifier("user_data")
85
+ column = QueryIdentifier("email_address")
86
+ print(f"SELECT {column} FROM {table}")
87
+ # -> SELECT "email_address" FROM "user_data"
88
+
89
+ # Handles special characters and keywords safely:
90
+ reserved = QueryIdentifier("group")
91
+ print(str(reserved)) # -> "group"
92
+ ```
93
+ """
94
+
95
+ def process_value(self, value: str):
96
+ return f'"{value}"'
97
+
98
+
99
+ class QueryLiteral(QueryElementBase):
100
+ """
101
+ Represents a raw SQL literal that should be included in the query exactly as provided,
102
+ without any additional processing or escaping.
103
+
104
+ This class is used for parts of the query that are already properly formatted and
105
+ should not be modified, such as SQL functions, operators, or pre-processed strings.
106
+
107
+ Warning:
108
+ Be careful when using QueryLiteral with user input, as it bypasses SQL escaping.
109
+ It should primarily be used for trusted, programmatically generated SQL components.
110
+
111
+ ```python {{sticky: True}}
112
+ # Safe usage with SQL functions:
113
+ count = QueryLiteral("COUNT(*)")
114
+ print(f"SELECT {count} FROM users")
115
+ # -> SELECT COUNT(*) FROM users
116
+
117
+ # Complex SQL expressions:
118
+ case = QueryLiteral("CASE WHEN age > 18 THEN 'adult' ELSE 'minor' END")
119
+ print(f"SELECT name, {case} FROM users")
120
+ # -> SELECT name, CASE WHEN age > 18 THEN 'adult' ELSE 'minor' END FROM users
121
+ ```
122
+ """
123
+
124
+ def process_value(self, value: str):
125
+ return value
126
+
127
+
128
+ class SQLGenerator:
129
+ """
130
+ The SQLGenerator class provides a convenient way to generate SQL-safe strings for various
131
+ query elements. It acts as a singleton that handles the proper formatting and escaping
132
+ of table names, column names, and other SQL elements.
133
+
134
+ The class provides three main methods:
135
+ - __call__: For generating table-qualified column names
136
+ - select: For generating SELECT clause elements with proper aliases
137
+ - raw: For generating raw identifiers without table qualification
138
+
139
+ Each method handles both column and table inputs, applying appropriate formatting rules
140
+ to prevent SQL injection and ensure proper identifier quoting.
141
+
142
+ ```python {{sticky: True}}
143
+ # Basic usage with columns
144
+ sql(User.name) # -> "users"."name"
145
+ sql.select(User.name) # -> "users"."name" AS "users_name"
146
+ sql.raw(User.name) # -> "name"
147
+
148
+ # Basic usage with tables
149
+ sql(User) # -> "users"
150
+ sql.select(User) # -> "users"."id" as "users_id", "users"."name" as "users_name"
151
+ sql.raw(User) # -> "users"
152
+
153
+ # Usage in query building
154
+ query = f"SELECT {sql.select(User.name)} FROM {sql(User)}"
155
+ # -> SELECT "users"."name" AS "users_name" FROM "users"
156
+ ```
157
+ """
158
+
159
+ def __call__(self, obj) -> QueryElementBase:
160
+ """
161
+ Generate a table-qualified column name or table identifier. This is the default
162
+ format used in most SQL contexts where a column or table reference is needed.
163
+
164
+ For columns, the output includes the table name to prevent ambiguity in JOINs
165
+ and complex queries. For tables, it returns just the quoted table name.
166
+
167
+ :param obj: A column or table object
168
+ :return: A SQL-safe string representation
169
+
170
+ ```python {{sticky: True}}
171
+ # Column usage
172
+ sql(User.name)
173
+ # -> "users"."name"
174
+
175
+ sql(Post.title)
176
+ # -> "posts"."title"
177
+
178
+ # Table usage
179
+ sql(User)
180
+ # -> "users"
181
+
182
+ # In a query context
183
+ query = f"UPDATE {sql(User)} SET {sql(User.name)} = 'John'"
184
+ # -> UPDATE "users" SET "users"."name" = 'John'
185
+
186
+ # In a JOIN context
187
+ query = f"SELECT {sql(User.name)} FROM {sql(User)} JOIN {sql(Post)} ON {sql(User.id)} = {sql(Post.user_id)}"
188
+ # -> SELECT "users"."name" FROM "users" JOIN "posts" ON "users"."id" = "posts"."user_id"
189
+ ```
190
+ """
191
+ if is_column(obj):
192
+ table = QueryIdentifier(obj.root_model.get_table_name())
193
+ column = QueryIdentifier(obj.key)
194
+ return QueryLiteral(f"{table}.{column}")
195
+ elif is_base_table(obj):
196
+ return QueryIdentifier(obj.get_table_name())
197
+ else:
198
+ raise ValueError(f"Invalid type for sql: {type(obj)}")
199
+
200
+ def select(self, obj) -> QueryElementBase:
201
+ """
202
+ Generate a SQL-safe string for selecting fields with proper aliases. This format
203
+ is specifically designed for SELECT clauses where unique column aliases are needed
204
+ to prevent name collisions.
205
+
206
+ For columns, generates a table-qualified column with an alias that includes both
207
+ table and column names. For tables, generates a comma-separated list of all
208
+ columns with their aliases.
209
+
210
+ :param obj: A column or table object
211
+ :return: A SQL-safe string with proper SELECT clause formatting
212
+
213
+ ```python {{sticky: True}}
214
+ # Single column selection
215
+ sql.select(User.name)
216
+ # -> "users"."name" AS "users_name"
217
+
218
+ # Full table selection
219
+ sql.select(User)
220
+ # -> "users"."id" as "users_id", "users"."name" as "users_name", "users"."email" as "users_email"
221
+
222
+ # In a complex query with multiple tables
223
+ query = f'''
224
+ SELECT
225
+ {sql.select(User)},
226
+ {sql.select(Post.title)}
227
+ FROM {sql(User)}
228
+ JOIN {sql(Post)} ON {sql(User.id)} = {sql(Post.user_id)}
229
+ '''
230
+ # -> SELECT
231
+ # "users"."id" as "users_id", "users"."name" as "users_name",
232
+ # "posts"."title" AS "posts_title"
233
+ # FROM "users"
234
+ # JOIN "posts" ON "users"."id" = "posts"."user_id"
235
+ ```
236
+ """
237
+ if is_column(obj):
238
+ table = QueryIdentifier(obj.root_model.get_table_name())
239
+ column = QueryIdentifier(obj.key)
240
+ alias = QueryIdentifier(f"{obj.root_model.get_table_name()}_{obj.key}")
241
+ return QueryLiteral(f"{table}.{column} AS {alias}")
242
+ elif is_base_table(obj):
243
+ table_token = QueryIdentifier(obj.get_table_name())
244
+ select_fields: list[str] = []
245
+ for field_name in obj.get_client_fields():
246
+ field_token = QueryIdentifier(field_name)
247
+ return_field = QueryIdentifier(f"{obj.get_table_name()}_{field_name}")
248
+ select_fields.append(f"{table_token}.{field_token} AS {return_field}")
249
+ return QueryLiteral(", ".join(select_fields))
250
+ else:
251
+ raise ValueError(f"Invalid type for select: {type(obj)}")
252
+
253
+ def raw(self, obj) -> QueryElementBase:
254
+ """
255
+ Generate a raw identifier without table qualification. This is useful in specific
256
+ contexts where you need just the column or table name without any additional
257
+ qualification.
258
+
259
+ For columns, returns just the column name without the table prefix. For tables,
260
+ returns just the table name. All identifiers are still properly quoted.
261
+
262
+ :param obj: A column or table object
263
+ :return: A SQL-safe raw identifier
264
+
265
+ ```python {{sticky: True}}
266
+ # Column usage
267
+ sql.raw(User.name)
268
+ # -> "name"
269
+
270
+ sql.raw(Post.title)
271
+ # -> "title"
272
+
273
+ # Table usage
274
+ sql.raw(User)
275
+ # -> "users"
276
+
277
+ # Useful in specific contexts like ORDER BY or GROUP BY
278
+ query = f"SELECT {sql.select(User.name)} FROM {sql(User)} ORDER BY {sql.raw(User.name)}"
279
+ # -> SELECT "users"."name" AS "users_name" FROM "users" ORDER BY "name"
280
+
281
+ # Or in UPDATE SET clauses where table qualification isn't needed
282
+ query = f"UPDATE {sql(User)} SET {sql.raw(User.name)} = 'John'"
283
+ # -> UPDATE "users" SET "name" = 'John'
284
+ ```
285
+ """
286
+ if is_column(obj):
287
+ return QueryIdentifier(obj.key)
288
+ elif is_base_table(obj):
289
+ return QueryIdentifier(obj.get_table_name())
290
+ else:
291
+ raise ValueError(f"Invalid type for raw: {type(obj)}")
292
+
293
+
294
+ sql = SQLGenerator()
File without changes