sqlphilosophy 0.1.0__tar.gz → 0.1.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.
Files changed (49) hide show
  1. sqlphilosophy-0.1.2/PKG-INFO +217 -0
  2. sqlphilosophy-0.1.2/README.md +178 -0
  3. {sqlphilosophy-0.1.0 → sqlphilosophy-0.1.2}/pyproject.toml +15 -2
  4. sqlphilosophy-0.1.2/src/sqlphilosophy/VERSION +1 -0
  5. {sqlphilosophy-0.1.0 → sqlphilosophy-0.1.2}/src/sqlphilosophy/aio/query.py +51 -46
  6. {sqlphilosophy-0.1.0 → sqlphilosophy-0.1.2}/src/sqlphilosophy/aio/repository.py +23 -18
  7. {sqlphilosophy-0.1.0 → sqlphilosophy-0.1.2}/src/sqlphilosophy/audit/listener.py +3 -2
  8. {sqlphilosophy-0.1.0 → sqlphilosophy-0.1.2}/src/sqlphilosophy/sql.py +9 -6
  9. {sqlphilosophy-0.1.0 → sqlphilosophy-0.1.2}/src/sqlphilosophy/sync/query.py +61 -48
  10. {sqlphilosophy-0.1.0 → sqlphilosophy-0.1.2}/src/sqlphilosophy/sync/repository.py +19 -15
  11. {sqlphilosophy-0.1.0 → sqlphilosophy-0.1.2}/src/sqlphilosophy/types.py +11 -0
  12. sqlphilosophy-0.1.2/src/sqlphilosophy.egg-info/PKG-INFO +217 -0
  13. {sqlphilosophy-0.1.0 → sqlphilosophy-0.1.2}/src/sqlphilosophy.egg-info/SOURCES.txt +1 -0
  14. {sqlphilosophy-0.1.0 → sqlphilosophy-0.1.2}/src/sqlphilosophy.egg-info/requires.txt +6 -2
  15. {sqlphilosophy-0.1.0 → sqlphilosophy-0.1.2}/tests/test_async_repository.py +12 -0
  16. sqlphilosophy-0.1.2/tests/test_batch7a_behavior.py +155 -0
  17. {sqlphilosophy-0.1.0 → sqlphilosophy-0.1.2}/tests/test_coverage_gaps.py +6 -6
  18. {sqlphilosophy-0.1.0 → sqlphilosophy-0.1.2}/tests/test_import_contract.py +11 -9
  19. {sqlphilosophy-0.1.0 → sqlphilosophy-0.1.2}/tests/test_last_mile.py +4 -4
  20. {sqlphilosophy-0.1.0 → sqlphilosophy-0.1.2}/tests/test_sql_helpers.py +9 -4
  21. {sqlphilosophy-0.1.0 → sqlphilosophy-0.1.2}/tests/test_sql_row_edges.py +2 -2
  22. sqlphilosophy-0.1.0/PKG-INFO +0 -134
  23. sqlphilosophy-0.1.0/README.md +0 -99
  24. sqlphilosophy-0.1.0/src/sqlphilosophy/VERSION +0 -1
  25. sqlphilosophy-0.1.0/src/sqlphilosophy.egg-info/PKG-INFO +0 -134
  26. {sqlphilosophy-0.1.0 → sqlphilosophy-0.1.2}/LICENSE +0 -0
  27. {sqlphilosophy-0.1.0 → sqlphilosophy-0.1.2}/setup.cfg +0 -0
  28. {sqlphilosophy-0.1.0 → sqlphilosophy-0.1.2}/src/sqlphilosophy/__init__.py +0 -0
  29. {sqlphilosophy-0.1.0 → sqlphilosophy-0.1.2}/src/sqlphilosophy/aio/__init__.py +0 -0
  30. {sqlphilosophy-0.1.0 → sqlphilosophy-0.1.2}/src/sqlphilosophy/aio/protocols.py +0 -0
  31. {sqlphilosophy-0.1.0 → sqlphilosophy-0.1.2}/src/sqlphilosophy/audit/__init__.py +0 -0
  32. {sqlphilosophy-0.1.0 → sqlphilosophy-0.1.2}/src/sqlphilosophy/audit/context.py +0 -0
  33. {sqlphilosophy-0.1.0 → sqlphilosophy-0.1.2}/src/sqlphilosophy/audit/fields.py +0 -0
  34. {sqlphilosophy-0.1.0 → sqlphilosophy-0.1.2}/src/sqlphilosophy/audit/model.py +0 -0
  35. {sqlphilosophy-0.1.0 → sqlphilosophy-0.1.2}/src/sqlphilosophy/py.typed +0 -0
  36. {sqlphilosophy-0.1.0 → sqlphilosophy-0.1.2}/src/sqlphilosophy/sorting.py +0 -0
  37. {sqlphilosophy-0.1.0 → sqlphilosophy-0.1.2}/src/sqlphilosophy/sync/__init__.py +0 -0
  38. {sqlphilosophy-0.1.0 → sqlphilosophy-0.1.2}/src/sqlphilosophy/sync/protocols.py +0 -0
  39. {sqlphilosophy-0.1.0 → sqlphilosophy-0.1.2}/src/sqlphilosophy.egg-info/dependency_links.txt +0 -0
  40. {sqlphilosophy-0.1.0 → sqlphilosophy-0.1.2}/src/sqlphilosophy.egg-info/top_level.txt +0 -0
  41. {sqlphilosophy-0.1.0 → sqlphilosophy-0.1.2}/tests/test_async_last_mile.py +0 -0
  42. {sqlphilosophy-0.1.0 → sqlphilosophy-0.1.2}/tests/test_async_query.py +0 -0
  43. {sqlphilosophy-0.1.0 → sqlphilosophy-0.1.2}/tests/test_audit.py +0 -0
  44. {sqlphilosophy-0.1.0 → sqlphilosophy-0.1.2}/tests/test_close_coverage.py +0 -0
  45. {sqlphilosophy-0.1.0 → sqlphilosophy-0.1.2}/tests/test_final_coverage.py +0 -0
  46. {sqlphilosophy-0.1.0 → sqlphilosophy-0.1.2}/tests/test_package_boundaries.py +0 -0
  47. {sqlphilosophy-0.1.0 → sqlphilosophy-0.1.2}/tests/test_sorting.py +0 -0
  48. {sqlphilosophy-0.1.0 → sqlphilosophy-0.1.2}/tests/test_sync_query.py +0 -0
  49. {sqlphilosophy-0.1.0 → sqlphilosophy-0.1.2}/tests/test_sync_repository.py +0 -0
@@ -0,0 +1,217 @@
1
+ Metadata-Version: 2.4
2
+ Name: sqlphilosophy
3
+ Version: 0.1.2
4
+ Summary: Portable SQLAlchemy repository kit: sync and async CRUD, statement builders, sort/pagination, and SQL helpers.
5
+ Author-email: Josh Martin <denverprogrammer@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/SignalSafeSoftware/sqlphilosophy
8
+ Project-URL: Repository, https://github.com/SignalSafeSoftware/sqlphilosophy
9
+ Project-URL: Documentation, https://github.com/SignalSafeSoftware/sqlphilosophy#readme
10
+ Project-URL: Issues, https://github.com/SignalSafeSoftware/sqlphilosophy/issues
11
+ Project-URL: Changelog, https://github.com/SignalSafeSoftware/sqlphilosophy/blob/main/CHANGELOG.md
12
+ Keywords: sqlalchemy,repository-pattern,repository,orm,database,pagination,audit,async
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Topic :: Database
19
+ Classifier: Typing :: Typed
20
+ Requires-Python: <4.0,>=3.12
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Requires-Dist: sqlalchemy<3,>=2.0
24
+ Provides-Extra: async
25
+ Requires-Dist: greenlet>=3.0; extra == "async"
26
+ Provides-Extra: dev
27
+ Requires-Dist: pytest>=8; extra == "dev"
28
+ Requires-Dist: pytest-asyncio>=0.24; extra == "dev"
29
+ Requires-Dist: pytest-cov>=6; extra == "dev"
30
+ Requires-Dist: aiosqlite>=0.20; extra == "dev"
31
+ Requires-Dist: greenlet>=3.0; extra == "dev"
32
+ Requires-Dist: build>=1.2; extra == "dev"
33
+ Requires-Dist: twine>=5; extra == "dev"
34
+ Requires-Dist: flake8>=7; extra == "dev"
35
+ Requires-Dist: mypy>=1.11; extra == "dev"
36
+ Requires-Dist: bandit>=1.7; extra == "dev"
37
+ Requires-Dist: types-greenlet>=3.0; extra == "dev"
38
+ Dynamic: license-file
39
+
40
+ # sqlphilosophy
41
+
42
+ Portable SQLAlchemy repository kit: sync and async CRUD, fluent statement builders, sort/pagination, Core SQL helpers, and optional audit listeners.
43
+
44
+ | | |
45
+ |---|---|
46
+ | **PyPI** | [`sqlphilosophy`](https://pypi.org/project/sqlphilosophy/) |
47
+ | **GitHub** | [SignalSafeSoftware/sqlphilosophy](https://github.com/SignalSafeSoftware/sqlphilosophy) |
48
+ | **Import** | `sqlphilosophy` (explicit submodules — no root re-exports) |
49
+ | **Python** | 3.12+ |
50
+ | **License** | MIT — see [LICENSE](./LICENSE) |
51
+
52
+ ## What this package does
53
+
54
+ - **Repository pattern** for a single mapped model (`BaseRepository`, `AsyncBaseRepository`).
55
+ - **Fluent query builders** with pagination/sort (`StatementQueryBuilder`, `ListQuery`, `SortConfig`).
56
+ - **SQL helpers** for row mapping, partial updates, filters, and developer-defined raw SQL fragments.
57
+ - **Optional audit listeners** and timestamp mixins.
58
+
59
+ ## What this package does not do
60
+
61
+ - Migrations, schema design, or connection pooling configuration.
62
+ - Authorization, multi-tenant isolation, or query sandboxing.
63
+ - Automatic commits for normal CRUD — see [Transaction ownership](#transaction-ownership) below.
64
+
65
+ ## Install
66
+
67
+ ```bash
68
+ pip install sqlphilosophy
69
+ ```
70
+
71
+ Async ORM (`AsyncSession`) also needs greenlet:
72
+
73
+ ```bash
74
+ pip install sqlphilosophy[async]
75
+ ```
76
+
77
+ Requires Python 3.12+ and SQLAlchemy 2.x.
78
+
79
+ ## Full example (sync model + session)
80
+
81
+ ```python
82
+ from sqlalchemy import String, create_engine
83
+ from sqlalchemy.orm import DeclarativeBase, Mapped, Session, mapped_column, sessionmaker
84
+
85
+ from sqlphilosophy.sorting import ListQuery
86
+ from sqlphilosophy.sync.repository import BaseRepository
87
+
88
+
89
+ class Base(DeclarativeBase):
90
+ pass
91
+
92
+
93
+ class Widget(Base):
94
+ __tablename__ = "widget"
95
+ id: Mapped[int] = mapped_column(primary_key=True)
96
+ name: Mapped[str] = mapped_column(String(64))
97
+
98
+
99
+ engine = create_engine("sqlite:///:memory:", future=True)
100
+ Base.metadata.create_all(engine)
101
+ SessionLocal = sessionmaker(bind=engine, expire_on_commit=False)
102
+
103
+ with SessionLocal() as session:
104
+ repo = BaseRepository(Widget, session)
105
+ widget = repo.create(name="alpha") # stages + flush; does not commit
106
+ session.commit()
107
+
108
+ page = repo.statement().fetch_page(ListQuery.from_page(page=1, size=20))
109
+ assert page.total >= 1
110
+ ```
111
+
112
+ **Async:** swap `Session` → `AsyncSession`, `BaseRepository` → `AsyncBaseRepository` from `sqlphilosophy.aio.repository`, and `await` repository methods.
113
+
114
+ ## Package layout
115
+
116
+ | Module | Contents |
117
+ |--------|----------|
118
+ | `sqlphilosophy.types` | Portable typing aliases (`RowMapping`, `PrimaryKey`, `SqlFilter`, …) |
119
+ | `sqlphilosophy.sql` | Row mapping helpers, partial updates, Core table helpers, filter builders |
120
+ | `sqlphilosophy.sorting` | `ListQuery`, `SortConfig`, `SortSpec`, pagination/sort resolution |
121
+ | `sqlphilosophy.sync` | Sync `BaseRepository`, `StatementQueryBuilder`, `RepositoryFactory` protocol |
122
+ | `sqlphilosophy.aio` | Async `AsyncBaseRepository`, `AsyncStatementQueryBuilder`, `AsyncRepositoryFactory` |
123
+ | `sqlphilosophy.audit` | Optional SQLAlchemy audit listeners and timestamp mixins |
124
+
125
+ ## Sync usage
126
+
127
+ ```python
128
+ from sqlalchemy.orm import Session
129
+
130
+ from sqlphilosophy.sorting import ListQuery, SortConfig, SortSpec
131
+ from sqlphilosophy.sql import partial_update_model, row_int
132
+ from sqlphilosophy.sync.protocols import RepositoryFactory
133
+ from sqlphilosophy.sync.repository import BaseRepository
134
+ from sqlphilosophy.sync.query import SqlAlchemyStatementBuilder
135
+
136
+ repo = BaseRepository(User, session)
137
+ rows = repo.statement().where(User.active.is_(True)).mappings().all()
138
+
139
+ repo = BaseRepository(User, session, factory)
140
+ page = repo.statement().fetch_page(ListQuery.from_page(page=1, size=20))
141
+ other = repo.for_repo(OrderRepository)
142
+ ```
143
+
144
+ ## Async usage
145
+
146
+ ```python
147
+ from sqlalchemy.ext.asyncio import AsyncSession
148
+
149
+ from sqlphilosophy.aio.repository import AsyncBaseRepository
150
+
151
+ repo = AsyncBaseRepository(User, session)
152
+ rows = await repo.statement().where(User.active.is_(True)).mappings().all()
153
+ ```
154
+
155
+ ## Transaction ownership
156
+
157
+ - **`create` / `update` / `delete` helpers** on repositories call `session.flush()` but **do not commit** unless documented otherwise.
158
+ - **`delete_all()`** executes a bulk delete and **does not commit** — the caller owns `session.commit()` / `rollback()` for the work unit.
159
+ - **`batched_purge_ids(...)`** deletes matching rows in batches and **commits after each batch** — treat it as a destructive, application-level operation you must authorize first.
160
+ - Your application owns **`session.commit()` / `rollback()`** for normal request/work-unit boundaries.
161
+
162
+ ## Raw SQL trust boundaries
163
+
164
+ The following must be **developer-defined** and must **never** be built from end-user input:
165
+
166
+ - Raw SQL fragments passed to SQL helper functions
167
+ - Literal column names, table names, and `ORDER BY` expressions
168
+ - Sort field allowlists wired into query builders
169
+
170
+ **User-supplied values must use bind parameters** (SQLAlchemy bound values), not string concatenation into SQL text or identifiers. See [SECURITY.md](./SECURITY.md).
171
+
172
+ ## Destructive helpers
173
+
174
+ - **`delete_all()`** — removes all rows for the repository model (sync and async variants). Does **not** commit; caller must commit or roll back.
175
+ - **`batched_purge_ids(...)`** — deletes matching rows in batches and commits each batch.
176
+
177
+ Call only after your application has authorized the operation. These helpers assume the caller understands the data loss impact.
178
+
179
+ ## Audit mixins
180
+
181
+ ```python
182
+ from sqlphilosophy.audit.context import audit_context
183
+ from sqlphilosophy.audit.listener import configure_audit_listeners
184
+ from sqlphilosophy.audit.model import TimestampModel
185
+
186
+ configure_audit_listeners()
187
+
188
+ with audit_context(actor_id=42):
189
+ session.add(MyModel(name="example"))
190
+ session.flush()
191
+ session.commit()
192
+ ```
193
+
194
+ Audit listeners record changes; they do **not** enforce access control.
195
+
196
+ ## Development
197
+
198
+ This repo uses [uv](https://docs.astral.sh/uv/):
199
+
200
+ ```bash
201
+ uv sync --extra dev
202
+ uv run pytest
203
+ uv run flake8 .
204
+ uv run python -m build
205
+ ```
206
+
207
+ ## Security
208
+
209
+ See [SECURITY.md](./SECURITY.md) for vulnerability reporting and SQL trust boundaries.
210
+
211
+ ## Releasing
212
+
213
+ See [RELEASING.md](./RELEASING.md) for GitHub + PyPI trusted publishing. See [CHANGELOG.md](./CHANGELOG.md).
214
+
215
+ ## License
216
+
217
+ MIT — see [LICENSE](./LICENSE).
@@ -0,0 +1,178 @@
1
+ # sqlphilosophy
2
+
3
+ Portable SQLAlchemy repository kit: sync and async CRUD, fluent statement builders, sort/pagination, Core SQL helpers, and optional audit listeners.
4
+
5
+ | | |
6
+ |---|---|
7
+ | **PyPI** | [`sqlphilosophy`](https://pypi.org/project/sqlphilosophy/) |
8
+ | **GitHub** | [SignalSafeSoftware/sqlphilosophy](https://github.com/SignalSafeSoftware/sqlphilosophy) |
9
+ | **Import** | `sqlphilosophy` (explicit submodules — no root re-exports) |
10
+ | **Python** | 3.12+ |
11
+ | **License** | MIT — see [LICENSE](./LICENSE) |
12
+
13
+ ## What this package does
14
+
15
+ - **Repository pattern** for a single mapped model (`BaseRepository`, `AsyncBaseRepository`).
16
+ - **Fluent query builders** with pagination/sort (`StatementQueryBuilder`, `ListQuery`, `SortConfig`).
17
+ - **SQL helpers** for row mapping, partial updates, filters, and developer-defined raw SQL fragments.
18
+ - **Optional audit listeners** and timestamp mixins.
19
+
20
+ ## What this package does not do
21
+
22
+ - Migrations, schema design, or connection pooling configuration.
23
+ - Authorization, multi-tenant isolation, or query sandboxing.
24
+ - Automatic commits for normal CRUD — see [Transaction ownership](#transaction-ownership) below.
25
+
26
+ ## Install
27
+
28
+ ```bash
29
+ pip install sqlphilosophy
30
+ ```
31
+
32
+ Async ORM (`AsyncSession`) also needs greenlet:
33
+
34
+ ```bash
35
+ pip install sqlphilosophy[async]
36
+ ```
37
+
38
+ Requires Python 3.12+ and SQLAlchemy 2.x.
39
+
40
+ ## Full example (sync model + session)
41
+
42
+ ```python
43
+ from sqlalchemy import String, create_engine
44
+ from sqlalchemy.orm import DeclarativeBase, Mapped, Session, mapped_column, sessionmaker
45
+
46
+ from sqlphilosophy.sorting import ListQuery
47
+ from sqlphilosophy.sync.repository import BaseRepository
48
+
49
+
50
+ class Base(DeclarativeBase):
51
+ pass
52
+
53
+
54
+ class Widget(Base):
55
+ __tablename__ = "widget"
56
+ id: Mapped[int] = mapped_column(primary_key=True)
57
+ name: Mapped[str] = mapped_column(String(64))
58
+
59
+
60
+ engine = create_engine("sqlite:///:memory:", future=True)
61
+ Base.metadata.create_all(engine)
62
+ SessionLocal = sessionmaker(bind=engine, expire_on_commit=False)
63
+
64
+ with SessionLocal() as session:
65
+ repo = BaseRepository(Widget, session)
66
+ widget = repo.create(name="alpha") # stages + flush; does not commit
67
+ session.commit()
68
+
69
+ page = repo.statement().fetch_page(ListQuery.from_page(page=1, size=20))
70
+ assert page.total >= 1
71
+ ```
72
+
73
+ **Async:** swap `Session` → `AsyncSession`, `BaseRepository` → `AsyncBaseRepository` from `sqlphilosophy.aio.repository`, and `await` repository methods.
74
+
75
+ ## Package layout
76
+
77
+ | Module | Contents |
78
+ |--------|----------|
79
+ | `sqlphilosophy.types` | Portable typing aliases (`RowMapping`, `PrimaryKey`, `SqlFilter`, …) |
80
+ | `sqlphilosophy.sql` | Row mapping helpers, partial updates, Core table helpers, filter builders |
81
+ | `sqlphilosophy.sorting` | `ListQuery`, `SortConfig`, `SortSpec`, pagination/sort resolution |
82
+ | `sqlphilosophy.sync` | Sync `BaseRepository`, `StatementQueryBuilder`, `RepositoryFactory` protocol |
83
+ | `sqlphilosophy.aio` | Async `AsyncBaseRepository`, `AsyncStatementQueryBuilder`, `AsyncRepositoryFactory` |
84
+ | `sqlphilosophy.audit` | Optional SQLAlchemy audit listeners and timestamp mixins |
85
+
86
+ ## Sync usage
87
+
88
+ ```python
89
+ from sqlalchemy.orm import Session
90
+
91
+ from sqlphilosophy.sorting import ListQuery, SortConfig, SortSpec
92
+ from sqlphilosophy.sql import partial_update_model, row_int
93
+ from sqlphilosophy.sync.protocols import RepositoryFactory
94
+ from sqlphilosophy.sync.repository import BaseRepository
95
+ from sqlphilosophy.sync.query import SqlAlchemyStatementBuilder
96
+
97
+ repo = BaseRepository(User, session)
98
+ rows = repo.statement().where(User.active.is_(True)).mappings().all()
99
+
100
+ repo = BaseRepository(User, session, factory)
101
+ page = repo.statement().fetch_page(ListQuery.from_page(page=1, size=20))
102
+ other = repo.for_repo(OrderRepository)
103
+ ```
104
+
105
+ ## Async usage
106
+
107
+ ```python
108
+ from sqlalchemy.ext.asyncio import AsyncSession
109
+
110
+ from sqlphilosophy.aio.repository import AsyncBaseRepository
111
+
112
+ repo = AsyncBaseRepository(User, session)
113
+ rows = await repo.statement().where(User.active.is_(True)).mappings().all()
114
+ ```
115
+
116
+ ## Transaction ownership
117
+
118
+ - **`create` / `update` / `delete` helpers** on repositories call `session.flush()` but **do not commit** unless documented otherwise.
119
+ - **`delete_all()`** executes a bulk delete and **does not commit** — the caller owns `session.commit()` / `rollback()` for the work unit.
120
+ - **`batched_purge_ids(...)`** deletes matching rows in batches and **commits after each batch** — treat it as a destructive, application-level operation you must authorize first.
121
+ - Your application owns **`session.commit()` / `rollback()`** for normal request/work-unit boundaries.
122
+
123
+ ## Raw SQL trust boundaries
124
+
125
+ The following must be **developer-defined** and must **never** be built from end-user input:
126
+
127
+ - Raw SQL fragments passed to SQL helper functions
128
+ - Literal column names, table names, and `ORDER BY` expressions
129
+ - Sort field allowlists wired into query builders
130
+
131
+ **User-supplied values must use bind parameters** (SQLAlchemy bound values), not string concatenation into SQL text or identifiers. See [SECURITY.md](./SECURITY.md).
132
+
133
+ ## Destructive helpers
134
+
135
+ - **`delete_all()`** — removes all rows for the repository model (sync and async variants). Does **not** commit; caller must commit or roll back.
136
+ - **`batched_purge_ids(...)`** — deletes matching rows in batches and commits each batch.
137
+
138
+ Call only after your application has authorized the operation. These helpers assume the caller understands the data loss impact.
139
+
140
+ ## Audit mixins
141
+
142
+ ```python
143
+ from sqlphilosophy.audit.context import audit_context
144
+ from sqlphilosophy.audit.listener import configure_audit_listeners
145
+ from sqlphilosophy.audit.model import TimestampModel
146
+
147
+ configure_audit_listeners()
148
+
149
+ with audit_context(actor_id=42):
150
+ session.add(MyModel(name="example"))
151
+ session.flush()
152
+ session.commit()
153
+ ```
154
+
155
+ Audit listeners record changes; they do **not** enforce access control.
156
+
157
+ ## Development
158
+
159
+ This repo uses [uv](https://docs.astral.sh/uv/):
160
+
161
+ ```bash
162
+ uv sync --extra dev
163
+ uv run pytest
164
+ uv run flake8 .
165
+ uv run python -m build
166
+ ```
167
+
168
+ ## Security
169
+
170
+ See [SECURITY.md](./SECURITY.md) for vulnerability reporting and SQL trust boundaries.
171
+
172
+ ## Releasing
173
+
174
+ See [RELEASING.md](./RELEASING.md) for GitHub + PyPI trusted publishing. See [CHANGELOG.md](./CHANGELOG.md).
175
+
176
+ ## License
177
+
178
+ MIT — see [LICENSE](./LICENSE).
@@ -34,11 +34,15 @@ async = [
34
34
  dev = [
35
35
  "pytest>=8",
36
36
  "pytest-asyncio>=0.24",
37
- "pytest-cov>=5",
37
+ "pytest-cov>=6",
38
38
  "aiosqlite>=0.20",
39
39
  "greenlet>=3.0",
40
40
  "build>=1.2",
41
- "twine>=6.1",
41
+ "twine>=5",
42
+ "flake8>=7",
43
+ "mypy>=1.11",
44
+ "bandit>=1.7",
45
+ "types-greenlet>=3.0",
42
46
  ]
43
47
 
44
48
  [project.urls]
@@ -64,6 +68,7 @@ addopts = "--cov=sqlphilosophy --cov-report=term-missing --cov-fail-under=100"
64
68
 
65
69
  [tool.coverage.run]
66
70
  source = ["sqlphilosophy"]
71
+ omit = ["scripts/*"]
67
72
  branch = false
68
73
 
69
74
  [tool.coverage.report]
@@ -72,3 +77,11 @@ show_missing = true
72
77
  exclude_also = [
73
78
  "raise NotImplementedError",
74
79
  ]
80
+
81
+ [tool.mypy]
82
+ python_version = "3.12"
83
+ plugins = ["sqlalchemy.ext.mypy.plugin"]
84
+ warn_unused_ignores = true
85
+
86
+ [tool.uv]
87
+ package = true
@@ -0,0 +1 @@
1
+ 0.1.2