iceaxe 0.6.0.dev2__tar.gz → 0.6.0.dev3__tar.gz

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 (80) hide show
  1. {iceaxe-0.6.0.dev2/iceaxe.egg-info → iceaxe-0.6.0.dev3}/PKG-INFO +1 -1
  2. {iceaxe-0.6.0.dev2 → iceaxe-0.6.0.dev3}/iceaxe/__tests__/test_comparison.py +60 -0
  3. {iceaxe-0.6.0.dev2 → iceaxe-0.6.0.dev3}/iceaxe/__tests__/test_queries.py +3 -3
  4. {iceaxe-0.6.0.dev2 → iceaxe-0.6.0.dev3}/iceaxe/comparison.py +56 -10
  5. {iceaxe-0.6.0.dev2 → iceaxe-0.6.0.dev3}/iceaxe/queries.py +6 -3
  6. {iceaxe-0.6.0.dev2 → iceaxe-0.6.0.dev3/iceaxe.egg-info}/PKG-INFO +1 -1
  7. {iceaxe-0.6.0.dev2 → iceaxe-0.6.0.dev3}/pyproject.toml +1 -1
  8. {iceaxe-0.6.0.dev2 → iceaxe-0.6.0.dev3}/LICENSE +0 -0
  9. {iceaxe-0.6.0.dev2 → iceaxe-0.6.0.dev3}/MANIFEST.in +0 -0
  10. {iceaxe-0.6.0.dev2 → iceaxe-0.6.0.dev3}/README.md +0 -0
  11. {iceaxe-0.6.0.dev2 → iceaxe-0.6.0.dev3}/iceaxe/__init__.py +0 -0
  12. {iceaxe-0.6.0.dev2 → iceaxe-0.6.0.dev3}/iceaxe/__tests__/__init__.py +0 -0
  13. {iceaxe-0.6.0.dev2 → iceaxe-0.6.0.dev3}/iceaxe/__tests__/benchmarks/__init__.py +0 -0
  14. {iceaxe-0.6.0.dev2 → iceaxe-0.6.0.dev3}/iceaxe/__tests__/benchmarks/test_bulk_insert.py +0 -0
  15. {iceaxe-0.6.0.dev2 → iceaxe-0.6.0.dev3}/iceaxe/__tests__/benchmarks/test_select.py +0 -0
  16. {iceaxe-0.6.0.dev2 → iceaxe-0.6.0.dev3}/iceaxe/__tests__/conf_models.py +0 -0
  17. {iceaxe-0.6.0.dev2 → iceaxe-0.6.0.dev3}/iceaxe/__tests__/conftest.py +0 -0
  18. {iceaxe-0.6.0.dev2 → iceaxe-0.6.0.dev3}/iceaxe/__tests__/helpers.py +0 -0
  19. {iceaxe-0.6.0.dev2 → iceaxe-0.6.0.dev3}/iceaxe/__tests__/migrations/__init__.py +0 -0
  20. {iceaxe-0.6.0.dev2 → iceaxe-0.6.0.dev3}/iceaxe/__tests__/migrations/conftest.py +0 -0
  21. {iceaxe-0.6.0.dev2 → iceaxe-0.6.0.dev3}/iceaxe/__tests__/migrations/test_action_sorter.py +0 -0
  22. {iceaxe-0.6.0.dev2 → iceaxe-0.6.0.dev3}/iceaxe/__tests__/migrations/test_generator.py +0 -0
  23. {iceaxe-0.6.0.dev2 → iceaxe-0.6.0.dev3}/iceaxe/__tests__/migrations/test_generics.py +0 -0
  24. {iceaxe-0.6.0.dev2 → iceaxe-0.6.0.dev3}/iceaxe/__tests__/mountaineer/__init__.py +0 -0
  25. {iceaxe-0.6.0.dev2 → iceaxe-0.6.0.dev3}/iceaxe/__tests__/mountaineer/dependencies/__init__.py +0 -0
  26. {iceaxe-0.6.0.dev2 → iceaxe-0.6.0.dev3}/iceaxe/__tests__/mountaineer/dependencies/test_core.py +0 -0
  27. {iceaxe-0.6.0.dev2 → iceaxe-0.6.0.dev3}/iceaxe/__tests__/schemas/__init__.py +0 -0
  28. {iceaxe-0.6.0.dev2 → iceaxe-0.6.0.dev3}/iceaxe/__tests__/schemas/test_actions.py +0 -0
  29. {iceaxe-0.6.0.dev2 → iceaxe-0.6.0.dev3}/iceaxe/__tests__/schemas/test_cli.py +0 -0
  30. {iceaxe-0.6.0.dev2 → iceaxe-0.6.0.dev3}/iceaxe/__tests__/schemas/test_db_memory_serializer.py +0 -0
  31. {iceaxe-0.6.0.dev2 → iceaxe-0.6.0.dev3}/iceaxe/__tests__/schemas/test_db_serializer.py +0 -0
  32. {iceaxe-0.6.0.dev2 → iceaxe-0.6.0.dev3}/iceaxe/__tests__/schemas/test_db_stubs.py +0 -0
  33. {iceaxe-0.6.0.dev2 → iceaxe-0.6.0.dev3}/iceaxe/__tests__/test_alias.py +0 -0
  34. {iceaxe-0.6.0.dev2 → iceaxe-0.6.0.dev3}/iceaxe/__tests__/test_base.py +0 -0
  35. {iceaxe-0.6.0.dev2 → iceaxe-0.6.0.dev3}/iceaxe/__tests__/test_field.py +0 -0
  36. {iceaxe-0.6.0.dev2 → iceaxe-0.6.0.dev3}/iceaxe/__tests__/test_helpers.py +0 -0
  37. {iceaxe-0.6.0.dev2 → iceaxe-0.6.0.dev3}/iceaxe/__tests__/test_modifications.py +0 -0
  38. {iceaxe-0.6.0.dev2 → iceaxe-0.6.0.dev3}/iceaxe/__tests__/test_queries_str.py +0 -0
  39. {iceaxe-0.6.0.dev2 → iceaxe-0.6.0.dev3}/iceaxe/__tests__/test_session.py +0 -0
  40. {iceaxe-0.6.0.dev2 → iceaxe-0.6.0.dev3}/iceaxe/__tests__/test_text_search.py +0 -0
  41. {iceaxe-0.6.0.dev2 → iceaxe-0.6.0.dev3}/iceaxe/alias_values.py +0 -0
  42. {iceaxe-0.6.0.dev2 → iceaxe-0.6.0.dev3}/iceaxe/base.py +0 -0
  43. {iceaxe-0.6.0.dev2 → iceaxe-0.6.0.dev3}/iceaxe/field.py +0 -0
  44. {iceaxe-0.6.0.dev2 → iceaxe-0.6.0.dev3}/iceaxe/functions.py +0 -0
  45. {iceaxe-0.6.0.dev2 → iceaxe-0.6.0.dev3}/iceaxe/generics.py +0 -0
  46. {iceaxe-0.6.0.dev2 → iceaxe-0.6.0.dev3}/iceaxe/io.py +0 -0
  47. {iceaxe-0.6.0.dev2 → iceaxe-0.6.0.dev3}/iceaxe/logging.py +0 -0
  48. {iceaxe-0.6.0.dev2 → iceaxe-0.6.0.dev3}/iceaxe/migrations/__init__.py +0 -0
  49. {iceaxe-0.6.0.dev2 → iceaxe-0.6.0.dev3}/iceaxe/migrations/action_sorter.py +0 -0
  50. {iceaxe-0.6.0.dev2 → iceaxe-0.6.0.dev3}/iceaxe/migrations/cli.py +0 -0
  51. {iceaxe-0.6.0.dev2 → iceaxe-0.6.0.dev3}/iceaxe/migrations/client_io.py +0 -0
  52. {iceaxe-0.6.0.dev2 → iceaxe-0.6.0.dev3}/iceaxe/migrations/generator.py +0 -0
  53. {iceaxe-0.6.0.dev2 → iceaxe-0.6.0.dev3}/iceaxe/migrations/migration.py +0 -0
  54. {iceaxe-0.6.0.dev2 → iceaxe-0.6.0.dev3}/iceaxe/migrations/migrator.py +0 -0
  55. {iceaxe-0.6.0.dev2 → iceaxe-0.6.0.dev3}/iceaxe/modifications.py +0 -0
  56. {iceaxe-0.6.0.dev2 → iceaxe-0.6.0.dev3}/iceaxe/mountaineer/__init__.py +0 -0
  57. {iceaxe-0.6.0.dev2 → iceaxe-0.6.0.dev3}/iceaxe/mountaineer/cli.py +0 -0
  58. {iceaxe-0.6.0.dev2 → iceaxe-0.6.0.dev3}/iceaxe/mountaineer/config.py +0 -0
  59. {iceaxe-0.6.0.dev2 → iceaxe-0.6.0.dev3}/iceaxe/mountaineer/dependencies/__init__.py +0 -0
  60. {iceaxe-0.6.0.dev2 → iceaxe-0.6.0.dev3}/iceaxe/mountaineer/dependencies/core.py +0 -0
  61. {iceaxe-0.6.0.dev2 → iceaxe-0.6.0.dev3}/iceaxe/postgres.py +0 -0
  62. {iceaxe-0.6.0.dev2 → iceaxe-0.6.0.dev3}/iceaxe/py.typed +0 -0
  63. {iceaxe-0.6.0.dev2 → iceaxe-0.6.0.dev3}/iceaxe/queries_str.py +0 -0
  64. {iceaxe-0.6.0.dev2 → iceaxe-0.6.0.dev3}/iceaxe/schemas/__init__.py +0 -0
  65. {iceaxe-0.6.0.dev2 → iceaxe-0.6.0.dev3}/iceaxe/schemas/actions.py +0 -0
  66. {iceaxe-0.6.0.dev2 → iceaxe-0.6.0.dev3}/iceaxe/schemas/cli.py +0 -0
  67. {iceaxe-0.6.0.dev2 → iceaxe-0.6.0.dev3}/iceaxe/schemas/db_memory_serializer.py +0 -0
  68. {iceaxe-0.6.0.dev2 → iceaxe-0.6.0.dev3}/iceaxe/schemas/db_serializer.py +0 -0
  69. {iceaxe-0.6.0.dev2 → iceaxe-0.6.0.dev3}/iceaxe/schemas/db_stubs.py +0 -0
  70. {iceaxe-0.6.0.dev2 → iceaxe-0.6.0.dev3}/iceaxe/session.py +0 -0
  71. {iceaxe-0.6.0.dev2 → iceaxe-0.6.0.dev3}/iceaxe/session_optimized.c +0 -0
  72. {iceaxe-0.6.0.dev2 → iceaxe-0.6.0.dev3}/iceaxe/session_optimized.pyx +0 -0
  73. {iceaxe-0.6.0.dev2 → iceaxe-0.6.0.dev3}/iceaxe/sql_types.py +0 -0
  74. {iceaxe-0.6.0.dev2 → iceaxe-0.6.0.dev3}/iceaxe/typing.py +0 -0
  75. {iceaxe-0.6.0.dev2 → iceaxe-0.6.0.dev3}/iceaxe.egg-info/SOURCES.txt +0 -0
  76. {iceaxe-0.6.0.dev2 → iceaxe-0.6.0.dev3}/iceaxe.egg-info/dependency_links.txt +0 -0
  77. {iceaxe-0.6.0.dev2 → iceaxe-0.6.0.dev3}/iceaxe.egg-info/requires.txt +0 -0
  78. {iceaxe-0.6.0.dev2 → iceaxe-0.6.0.dev3}/iceaxe.egg-info/top_level.txt +0 -0
  79. {iceaxe-0.6.0.dev2 → iceaxe-0.6.0.dev3}/setup.cfg +0 -0
  80. {iceaxe-0.6.0.dev2 → iceaxe-0.6.0.dev3}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: iceaxe
3
- Version: 0.6.0.dev2
3
+ Version: 0.6.0.dev3
4
4
  Summary: A modern, fast ORM for Python.
5
5
  Author-email: Pierce Freeman <pierce@freeman.vc>
6
6
  Requires-Python: >=3.11
@@ -294,3 +294,63 @@ def test_default_eq_ne_are_null_safe(db_field: DBFieldClassDefinition):
294
294
  ne_col = db_field != other_field
295
295
  assert isinstance(ne_col, FieldComparison)
296
296
  assert ne_col.comparison == ComparisonType.IS_DISTINCT_FROM
297
+
298
+
299
+ @pytest.mark.parametrize(
300
+ "magic_method,value",
301
+ [
302
+ ("__eq__", 5),
303
+ ("__ne__", 5),
304
+ ("__lt__", 5),
305
+ ("__le__", 5),
306
+ ("__gt__", 5),
307
+ ("__ge__", 5),
308
+ ],
309
+ )
310
+ def test_python_magic_methods_set_expression_flag(
311
+ db_field: DBFieldClassDefinition, magic_method: str, value: Any
312
+ ):
313
+ """
314
+ Test that all Python magic methods set python_expression to True
315
+ """
316
+ comparison = getattr(db_field, magic_method)(value)
317
+ assert isinstance(comparison, FieldComparison)
318
+ assert comparison.python_expression is True
319
+
320
+
321
+ @pytest.mark.parametrize(
322
+ "initial_comparison, python_expression, expected_comparison",
323
+ [
324
+ (ComparisonType.IS_NOT_DISTINCT_FROM, True, ComparisonType.EQ),
325
+ (ComparisonType.IS_DISTINCT_FROM, True, ComparisonType.NE),
326
+ (
327
+ ComparisonType.IS_NOT_DISTINCT_FROM,
328
+ False,
329
+ ComparisonType.IS_NOT_DISTINCT_FROM,
330
+ ),
331
+ (ComparisonType.IS_DISTINCT_FROM, False, ComparisonType.IS_DISTINCT_FROM),
332
+ ],
333
+ )
334
+ def test_force_join_constraints(
335
+ initial_comparison: ComparisonType,
336
+ python_expression: bool,
337
+ expected_comparison: ComparisonType,
338
+ ):
339
+ """
340
+ Test that force_join_constraints correctly transforms comparison types
341
+ """
342
+ db_field = DBFieldClassDefinition(
343
+ root_model=TableBase, key="test_key", field_definition=DBFieldInfo()
344
+ )
345
+ other_field = DBFieldClassDefinition(
346
+ root_model=TableBase, key="other_key", field_definition=DBFieldInfo()
347
+ )
348
+
349
+ comparison = FieldComparison(
350
+ left=db_field,
351
+ comparison=initial_comparison,
352
+ right=other_field,
353
+ python_expression=python_expression,
354
+ )
355
+ forced = comparison.force_join_constraints()
356
+ assert forced.comparison == expected_comparison
@@ -102,7 +102,7 @@ def test_join():
102
102
  .join(ArtifactDemo, UserDemo.id == ArtifactDemo.user_id)
103
103
  )
104
104
  assert new_query.build() == (
105
- 'SELECT "userdemo"."id" AS "userdemo_id", "artifactdemo"."title" AS "artifactdemo_title" FROM "userdemo" INNER JOIN "artifactdemo" ON "userdemo"."id" IS NOT DISTINCT FROM "artifactdemo"."user_id"',
105
+ 'SELECT "userdemo"."id" AS "userdemo_id", "artifactdemo"."title" AS "artifactdemo_title" FROM "userdemo" INNER JOIN "artifactdemo" ON "userdemo"."id" = "artifactdemo"."user_id"',
106
106
  [],
107
107
  )
108
108
 
@@ -114,7 +114,7 @@ def test_left_join():
114
114
  .join(ArtifactDemo, UserDemo.id == ArtifactDemo.user_id, "LEFT")
115
115
  )
116
116
  assert new_query.build() == (
117
- 'SELECT "userdemo"."id" AS "userdemo_id", "artifactdemo"."title" AS "artifactdemo_title" FROM "userdemo" LEFT JOIN "artifactdemo" ON "userdemo"."id" IS NOT DISTINCT FROM "artifactdemo"."user_id"',
117
+ 'SELECT "userdemo"."id" AS "userdemo_id", "artifactdemo"."title" AS "artifactdemo_title" FROM "userdemo" LEFT JOIN "artifactdemo" ON "userdemo"."id" = "artifactdemo"."user_id"',
118
118
  [],
119
119
  )
120
120
 
@@ -525,7 +525,7 @@ def test_for_update_multiple_of():
525
525
  assert new_query.build() == (
526
526
  'SELECT "userdemo"."id" AS "userdemo_id", "userdemo"."name" AS "userdemo_name", "userdemo"."email" AS "userdemo_email", '
527
527
  '"artifactdemo"."id" AS "artifactdemo_id", "artifactdemo"."title" AS "artifactdemo_title", "artifactdemo"."user_id" AS "artifactdemo_user_id" '
528
- 'FROM "userdemo" INNER JOIN "artifactdemo" ON "userdemo"."id" IS NOT DISTINCT FROM "artifactdemo"."user_id" '
528
+ 'FROM "userdemo" INNER JOIN "artifactdemo" ON "userdemo"."id" = "artifactdemo"."user_id" '
529
529
  'FOR UPDATE OF "artifactdemo", "userdemo"',
530
530
  [],
531
531
  )
@@ -181,6 +181,14 @@ class FieldComparison(Generic[T]):
181
181
  The right side of the comparison (can be a value or another field)
182
182
  """
183
183
 
184
+ python_expression: bool = False
185
+ """
186
+ Implicit comparisons are created from Python expressions (like col1 == col2). If this
187
+ flag is False, it means the user explicitly used one of the column() helper functions like
188
+ .equals(), .not_equals(), etc.
189
+
190
+ """
191
+
184
192
  def to_query(self, start: int = 1) -> tuple[QueryLiteral, list[Any]]:
185
193
  """
186
194
  Converts the comparison to its SQL representation.
@@ -220,6 +228,24 @@ class FieldComparison(Generic[T]):
220
228
 
221
229
  return QueryLiteral(f"{field} {comparison.value} {value}"), variables
222
230
 
231
+ def force_join_constraints(self):
232
+ """
233
+ Set the context of the comparison to be used in a join. This places certain constraints
234
+ on the comparison operations that can be applied, like using equals for columns instead
235
+ of IS DISTINCT FROM.
236
+
237
+ """
238
+ comparison = self.comparison
239
+
240
+ # Only if we were created implicitly should we modify the comparison type
241
+ if self.python_expression:
242
+ if self.comparison == ComparisonType.IS_DISTINCT_FROM:
243
+ comparison = ComparisonType.NE
244
+ elif self.comparison == ComparisonType.IS_NOT_DISTINCT_FROM:
245
+ comparison = ComparisonType.EQ
246
+
247
+ return FieldComparison(left=self.left, comparison=comparison, right=self.right)
248
+
223
249
 
224
250
  @dataclass
225
251
  class FieldComparisonGroup:
@@ -318,11 +344,17 @@ class ComparisonBase(ABC, Generic[J]):
318
344
  :param other: Value to compare against
319
345
  :return: A field comparison object
320
346
  """
347
+ raw_comparison: bool | None = None
321
348
  if other is None:
322
- return self.is_(None)
349
+ raw_comparison = self.is_(None)
323
350
  elif is_column(other):
324
- return self.is_not_distinct_from(other)
325
- return self.equals(other)
351
+ raw_comparison = self.is_not_distinct_from(other)
352
+
353
+ comparison: FieldComparison[Self] = (
354
+ raw_comparison if raw_comparison is not None else self.equals(other)
355
+ ) # type: ignore
356
+ comparison.python_expression = True
357
+ return comparison
326
358
 
327
359
  def __ne__(self, other): # type: ignore
328
360
  """
@@ -333,11 +365,17 @@ class ComparisonBase(ABC, Generic[J]):
333
365
  :param other: Value to compare against
334
366
  :return: A field comparison object
335
367
  """
368
+ raw_comparison: bool | None = None
336
369
  if other is None:
337
- return self.is_not(None)
370
+ raw_comparison = self.is_not(None)
338
371
  elif is_column(other):
339
- return self.is_distinct_from(other)
340
- return self.not_equals(other)
372
+ raw_comparison = self.is_distinct_from(other)
373
+
374
+ comparison: FieldComparison[Self] = (
375
+ raw_comparison if raw_comparison is not None else self.not_equals(other)
376
+ ) # type: ignore
377
+ comparison.python_expression = True
378
+ return comparison
341
379
 
342
380
  def __lt__(self, other):
343
381
  """
@@ -347,7 +385,9 @@ class ComparisonBase(ABC, Generic[J]):
347
385
  :param other: Value to compare against
348
386
  :return: A field comparison object
349
387
  """
350
- return self._compare(ComparisonType.LT, other)
388
+ comparison = self._compare(ComparisonType.LT, other)
389
+ comparison.python_expression = True
390
+ return comparison
351
391
 
352
392
  def __le__(self, other):
353
393
  """
@@ -357,7 +397,9 @@ class ComparisonBase(ABC, Generic[J]):
357
397
  :param other: Value to compare against
358
398
  :return: A field comparison object
359
399
  """
360
- return self._compare(ComparisonType.LE, other)
400
+ comparison = self._compare(ComparisonType.LE, other)
401
+ comparison.python_expression = True
402
+ return comparison
361
403
 
362
404
  def __gt__(self, other):
363
405
  """
@@ -367,7 +409,9 @@ class ComparisonBase(ABC, Generic[J]):
367
409
  :param other: Value to compare against
368
410
  :return: A field comparison object
369
411
  """
370
- return self._compare(ComparisonType.GT, other)
412
+ comparison = self._compare(ComparisonType.GT, other)
413
+ comparison.python_expression = True
414
+ return comparison
371
415
 
372
416
  def __ge__(self, other):
373
417
  """
@@ -377,7 +421,9 @@ class ComparisonBase(ABC, Generic[J]):
377
421
  :param other: Value to compare against
378
422
  :return: A field comparison object
379
423
  """
380
- return self._compare(ComparisonType.GE, other)
424
+ comparison = self._compare(ComparisonType.GE, other)
425
+ comparison.python_expression = True
426
+ return comparison
381
427
 
382
428
  def equals(self, other: Any) -> bool:
383
429
  """
@@ -677,9 +677,12 @@ class QueryBuilder(Generic[P, QueryType]):
677
677
  f"Invalid join condition: {on}, should be MyTable.column == OtherTable.column"
678
678
  )
679
679
 
680
- on_left, _ = on.left.to_query()
681
- comparison = QueryLiteral(on.comparison.value)
682
- on_right, _ = on.right.to_query()
680
+ # Let the comparison update to handle its current usage in a join
681
+ on_join = on.force_join_constraints()
682
+
683
+ on_left, _ = on_join.left.to_query()
684
+ comparison = QueryLiteral(on_join.comparison.value)
685
+ on_right, _ = on_join.right.to_query()
683
686
 
684
687
  join_sql = f"{join_type} JOIN {sql(table)} ON {on_left} {comparison} {on_right}"
685
688
  self._join_clauses.append(join_sql)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: iceaxe
3
- Version: 0.6.0.dev2
3
+ Version: 0.6.0.dev3
4
4
  Summary: A modern, fast ORM for Python.
5
5
  Author-email: Pierce Freeman <pierce@freeman.vc>
6
6
  Requires-Python: >=3.11
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "iceaxe"
3
- version = "0.6.0.dev2"
3
+ version = "0.6.0.dev3"
4
4
  description = "A modern, fast ORM for Python."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes