sql_fusion 1.2.0__tar.gz → 1.2.2__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.
@@ -1,10 +1,10 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: sql_fusion
3
- Version: 1.2.0
3
+ Version: 1.2.2
4
4
  Summary: Python query builder with a focus on composability and reusability.
5
5
  Author: Mastermind-U
6
6
  Author-email: Mastermind-U <rex49513@gmail.com>
7
- Requires-Python: >=3.14
7
+ Requires-Python: >=3.11
8
8
  Project-URL: Homepage, https://github.com/Mastermind-U/sql_fusion
9
9
  Project-URL: Documentation, https://github.com/Mastermind-U/sql_fusion/blob/main/README.md
10
10
  Project-URL: Repository, https://github.com/Mastermind-U/sql_fusion.git
@@ -40,6 +40,7 @@ That makes it easy to plug into your own connection layer.
40
40
  - [Quickstart: psycopg3](#quickstart-psycopg3)
41
41
  - [Query Basics](#query-basics)
42
42
  - [Subquery Example](#subquery-example)
43
+ - [Set Operations](#set-operations)
43
44
  - [Method Reference](#method-reference)
44
45
  - [Functions](#functions)
45
46
  - [CTEs](#ctes)
@@ -71,6 +72,22 @@ SQL Fusion is built for the middle ground:
71
72
 
72
73
  In short, the goal is to keep the ergonomics of a lightweight builder while still covering the parts of SQL that matter in real applications.
73
74
 
75
+ ### Why not SQLAlchemy ORM or Core?
76
+
77
+ SQLAlchemy is an excellent tool, but it is also a much heavier and more universal system:
78
+
79
+ - it brings a larger abstraction surface than this project needs
80
+ - it is optimized for a broad ORM and Core ecosystem, not only for a small SQL builder
81
+ - some connectors and databases still do not have first-class SQLAlchemy integrations, which can make adoption less straightforward in mixed environments
82
+
83
+ SQLAlchemy Core is closer to SQL Fusion than the full ORM, but it still carries more machinery than this project is meant to expose:
84
+
85
+ - it is part of a broader ecosystem with dialects, compilation layers, and extra conventions
86
+ - it can feel more verbose when you only want a small chainable builder
87
+ - some connectors and databases still do not have smooth SQLAlchemy Core support, so portability can depend on the backend
88
+
89
+ SQL Fusion is intentionally narrower so it can stay lightweight, easy to embed, and practical for DB-API style backends without extra complexity.
90
+
74
91
  ## What You Get
75
92
 
76
93
  - `SELECT`, `INSERT`, `UPDATE`, and `DELETE` builders
@@ -404,6 +421,94 @@ paid_orders = (
404
421
  query, params = select().from_(paid_orders).compile()
405
422
  ```
406
423
 
424
+ ## Set Operations
425
+
426
+ SQL Fusion supports compound queries through three small wrapper classes:
427
+
428
+ - `union(query1, query2, all_=False, by_name=False)`
429
+ - `intersect(query1, query2, all_=False)`
430
+ - `except_(query1, query2, all_=False)`
431
+
432
+ Each builder accepts two query objects and returns a new query that compiles to
433
+ the matching SQL set operation.
434
+
435
+ ### `union(...)`
436
+
437
+ ```python
438
+ users = Table("users")
439
+ archived_users = Table("archived_users")
440
+
441
+ active_users = select(users.id, users.name).from_(users).where_by(status="active")
442
+ archived_active_users = (
443
+ select(archived_users.id, archived_users.name)
444
+ .from_(archived_users)
445
+ .where_by(status="active")
446
+ )
447
+
448
+ query, params = union(active_users, archived_active_users).compile()
449
+ ```
450
+
451
+ Use `all=True` for `UNION ALL`:
452
+
453
+ ```python
454
+ query, params = union(active_users, archived_active_users, all_=True).compile()
455
+ ```
456
+
457
+ Use `by_name=True` when the two result sets expose the same logical columns in
458
+ different orders:
459
+
460
+ ```python
461
+ left = select(users.id, users.name).from_(users)
462
+ right = select(archived_users.name, archived_users.id).from_(archived_users)
463
+
464
+ query, params = union(left, right, all_=True, by_name=True).compile()
465
+ ```
466
+
467
+ ### `intersect(...)`
468
+
469
+ ```python
470
+ users = Table("users")
471
+ premium_users = Table("premium_users")
472
+
473
+ active_users = select(users.id).from_(users).where_by(status="active")
474
+ premium_active_users = (
475
+ select(premium_users.id).from_(premium_users).where_by(status="active")
476
+ )
477
+
478
+ query, params = intersect(active_users, premium_active_users).compile()
479
+ ```
480
+
481
+ Use `all_=True` for `INTERSECT ALL`:
482
+
483
+ ```python
484
+ query, params = intersect(
485
+ active_users,
486
+ premium_active_users,
487
+ all_=True,
488
+ ).compile()
489
+ ```
490
+
491
+ ### `except_(...)`
492
+
493
+ ```python
494
+ users = Table("users")
495
+ banned_users = Table("banned_users")
496
+
497
+ all_users = select(users.id).from_(users)
498
+ banned_user_ids = select(banned_users.id).from_(banned_users)
499
+
500
+ query, params = except_(all_users, banned_user_ids).compile()
501
+ ```
502
+
503
+ Use `all_=True` for `EXCEPT ALL`:
504
+
505
+ ```python
506
+ query, params = except_(all_users, banned_user_ids, all_=True).compile()
507
+ ```
508
+
509
+ These builders preserve the parameter order from left to right, so the returned
510
+ `params` tuple can be passed directly to DB-API drivers.
511
+
407
512
  ### Having Example
408
513
 
409
514
  ```python
@@ -26,6 +26,7 @@ That makes it easy to plug into your own connection layer.
26
26
  - [Quickstart: psycopg3](#quickstart-psycopg3)
27
27
  - [Query Basics](#query-basics)
28
28
  - [Subquery Example](#subquery-example)
29
+ - [Set Operations](#set-operations)
29
30
  - [Method Reference](#method-reference)
30
31
  - [Functions](#functions)
31
32
  - [CTEs](#ctes)
@@ -57,6 +58,22 @@ SQL Fusion is built for the middle ground:
57
58
 
58
59
  In short, the goal is to keep the ergonomics of a lightweight builder while still covering the parts of SQL that matter in real applications.
59
60
 
61
+ ### Why not SQLAlchemy ORM or Core?
62
+
63
+ SQLAlchemy is an excellent tool, but it is also a much heavier and more universal system:
64
+
65
+ - it brings a larger abstraction surface than this project needs
66
+ - it is optimized for a broad ORM and Core ecosystem, not only for a small SQL builder
67
+ - some connectors and databases still do not have first-class SQLAlchemy integrations, which can make adoption less straightforward in mixed environments
68
+
69
+ SQLAlchemy Core is closer to SQL Fusion than the full ORM, but it still carries more machinery than this project is meant to expose:
70
+
71
+ - it is part of a broader ecosystem with dialects, compilation layers, and extra conventions
72
+ - it can feel more verbose when you only want a small chainable builder
73
+ - some connectors and databases still do not have smooth SQLAlchemy Core support, so portability can depend on the backend
74
+
75
+ SQL Fusion is intentionally narrower so it can stay lightweight, easy to embed, and practical for DB-API style backends without extra complexity.
76
+
60
77
  ## What You Get
61
78
 
62
79
  - `SELECT`, `INSERT`, `UPDATE`, and `DELETE` builders
@@ -390,6 +407,94 @@ paid_orders = (
390
407
  query, params = select().from_(paid_orders).compile()
391
408
  ```
392
409
 
410
+ ## Set Operations
411
+
412
+ SQL Fusion supports compound queries through three small wrapper classes:
413
+
414
+ - `union(query1, query2, all_=False, by_name=False)`
415
+ - `intersect(query1, query2, all_=False)`
416
+ - `except_(query1, query2, all_=False)`
417
+
418
+ Each builder accepts two query objects and returns a new query that compiles to
419
+ the matching SQL set operation.
420
+
421
+ ### `union(...)`
422
+
423
+ ```python
424
+ users = Table("users")
425
+ archived_users = Table("archived_users")
426
+
427
+ active_users = select(users.id, users.name).from_(users).where_by(status="active")
428
+ archived_active_users = (
429
+ select(archived_users.id, archived_users.name)
430
+ .from_(archived_users)
431
+ .where_by(status="active")
432
+ )
433
+
434
+ query, params = union(active_users, archived_active_users).compile()
435
+ ```
436
+
437
+ Use `all=True` for `UNION ALL`:
438
+
439
+ ```python
440
+ query, params = union(active_users, archived_active_users, all_=True).compile()
441
+ ```
442
+
443
+ Use `by_name=True` when the two result sets expose the same logical columns in
444
+ different orders:
445
+
446
+ ```python
447
+ left = select(users.id, users.name).from_(users)
448
+ right = select(archived_users.name, archived_users.id).from_(archived_users)
449
+
450
+ query, params = union(left, right, all_=True, by_name=True).compile()
451
+ ```
452
+
453
+ ### `intersect(...)`
454
+
455
+ ```python
456
+ users = Table("users")
457
+ premium_users = Table("premium_users")
458
+
459
+ active_users = select(users.id).from_(users).where_by(status="active")
460
+ premium_active_users = (
461
+ select(premium_users.id).from_(premium_users).where_by(status="active")
462
+ )
463
+
464
+ query, params = intersect(active_users, premium_active_users).compile()
465
+ ```
466
+
467
+ Use `all_=True` for `INTERSECT ALL`:
468
+
469
+ ```python
470
+ query, params = intersect(
471
+ active_users,
472
+ premium_active_users,
473
+ all_=True,
474
+ ).compile()
475
+ ```
476
+
477
+ ### `except_(...)`
478
+
479
+ ```python
480
+ users = Table("users")
481
+ banned_users = Table("banned_users")
482
+
483
+ all_users = select(users.id).from_(users)
484
+ banned_user_ids = select(banned_users.id).from_(banned_users)
485
+
486
+ query, params = except_(all_users, banned_user_ids).compile()
487
+ ```
488
+
489
+ Use `all_=True` for `EXCEPT ALL`:
490
+
491
+ ```python
492
+ query, params = except_(all_users, banned_user_ids, all_=True).compile()
493
+ ```
494
+
495
+ These builders preserve the parameter order from left to right, so the returned
496
+ `params` tuple can be passed directly to DB-API drivers.
497
+
393
498
  ### Having Example
394
499
 
395
500
  ```python
@@ -1,10 +1,10 @@
1
1
  [project]
2
2
  name = "sql_fusion"
3
- version = "1.2.0"
3
+ version = "1.2.2"
4
4
  description = "Python query builder with a focus on composability and reusability."
5
5
  readme = "README.md"
6
6
  authors = [{ name = "Mastermind-U", email = "rex49513@gmail.com" }]
7
- requires-python = ">=3.14"
7
+ requires-python = ">=3.11"
8
8
  dependencies = []
9
9
 
10
10
  [project.urls]
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  from collections.abc import Iterable
2
4
  from copy import copy
3
5
  from typing import Any, Callable, Self
@@ -57,11 +57,11 @@ class union(_set_operation):
57
57
  self,
58
58
  query1: AbstractQuery,
59
59
  query2: AbstractQuery,
60
- all: bool = False, # noqa: A002
60
+ all_: bool = False,
61
61
  by_name: bool = False,
62
62
  ) -> None:
63
63
  super().__init__(query1, query2)
64
- self._all = all
64
+ self._all = all_
65
65
  self._by_name = by_name
66
66
 
67
67
  def _operator_sql(self) -> str: