admin-litestar 0.1.0__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 (46) hide show
  1. admin_litestar-0.1.0/.gitignore +11 -0
  2. admin_litestar-0.1.0/LICENSE +21 -0
  3. admin_litestar-0.1.0/PKG-INFO +239 -0
  4. admin_litestar-0.1.0/README.md +189 -0
  5. admin_litestar-0.1.0/pyproject.toml +54 -0
  6. admin_litestar-0.1.0/src/admin_litestar/__init__.py +50 -0
  7. admin_litestar-0.1.0/src/admin_litestar/admin.py +183 -0
  8. admin_litestar-0.1.0/src/admin_litestar/auth.py +107 -0
  9. admin_litestar-0.1.0/src/admin_litestar/constants.py +47 -0
  10. admin_litestar-0.1.0/src/admin_litestar/controllers/__init__.py +6 -0
  11. admin_litestar-0.1.0/src/admin_litestar/controllers/models.py +176 -0
  12. admin_litestar-0.1.0/src/admin_litestar/controllers/session.py +77 -0
  13. admin_litestar-0.1.0/src/admin_litestar/export.py +45 -0
  14. admin_litestar-0.1.0/src/admin_litestar/pages.py +28 -0
  15. admin_litestar-0.1.0/src/admin_litestar/passwords.py +76 -0
  16. admin_litestar-0.1.0/src/admin_litestar/protocols.py +63 -0
  17. admin_litestar-0.1.0/src/admin_litestar/py.typed +0 -0
  18. admin_litestar-0.1.0/src/admin_litestar/queries.py +161 -0
  19. admin_litestar-0.1.0/src/admin_litestar/render.py +41 -0
  20. admin_litestar-0.1.0/src/admin_litestar/spec.py +145 -0
  21. admin_litestar-0.1.0/src/admin_litestar/static/__init__.py +7 -0
  22. admin_litestar-0.1.0/src/admin_litestar/static/admin.css +205 -0
  23. admin_litestar-0.1.0/src/admin_litestar/static/htmx.min.js +1 -0
  24. admin_litestar-0.1.0/src/admin_litestar/templates/__init__.py +7 -0
  25. admin_litestar-0.1.0/src/admin_litestar/templates/_table.html +18 -0
  26. admin_litestar-0.1.0/src/admin_litestar/templates/base.html +16 -0
  27. admin_litestar-0.1.0/src/admin_litestar/templates/base_bare.html +10 -0
  28. admin_litestar-0.1.0/src/admin_litestar/templates/detail.html +21 -0
  29. admin_litestar-0.1.0/src/admin_litestar/templates/list.html +25 -0
  30. admin_litestar-0.1.0/src/admin_litestar/templates/login.html +13 -0
  31. admin_litestar-0.1.0/src/admin_litestar/templates/nav.html +20 -0
  32. admin_litestar-0.1.0/tests/__init__.py +1 -0
  33. admin_litestar-0.1.0/tests/conftest.py +1 -0
  34. admin_litestar-0.1.0/tests/hostapp.py +111 -0
  35. admin_litestar-0.1.0/tests/models.py +48 -0
  36. admin_litestar-0.1.0/tests/test_admin.py +140 -0
  37. admin_litestar-0.1.0/tests/test_api.py +88 -0
  38. admin_litestar-0.1.0/tests/test_auth.py +159 -0
  39. admin_litestar-0.1.0/tests/test_boundary.py +162 -0
  40. admin_litestar-0.1.0/tests/test_export.py +51 -0
  41. admin_litestar-0.1.0/tests/test_models_controller.py +233 -0
  42. admin_litestar-0.1.0/tests/test_passwords.py +65 -0
  43. admin_litestar-0.1.0/tests/test_queries.py +158 -0
  44. admin_litestar-0.1.0/tests/test_render.py +84 -0
  45. admin_litestar-0.1.0/tests/test_spec.py +222 -0
  46. admin_litestar-0.1.0/tests/test_templates.py +58 -0
@@ -0,0 +1,11 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ .pytest_cache/
4
+ .mypy_cache/
5
+ .ruff_cache/
6
+ .venv/
7
+ dist/
8
+ build/
9
+ *.egg-info/
10
+ .coverage
11
+ htmlcov/
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Omirtay Adilkhan
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,239 @@
1
+ Metadata-Version: 2.4
2
+ Name: admin-litestar
3
+ Version: 0.1.0
4
+ Summary: Server-rendered admin for Litestar + SQLAlchemy applications
5
+ Project-URL: Homepage, https://github.com/adllkhan/admin-litestar
6
+ Project-URL: Repository, https://github.com/adllkhan/admin-litestar
7
+ Project-URL: Issues, https://github.com/adllkhan/admin-litestar/issues
8
+ Author-email: Omirtay Adilkhan <aomertayevich@gmail.com>
9
+ License: MIT License
10
+
11
+ Copyright (c) 2026 Omirtay Adilkhan
12
+
13
+ Permission is hereby granted, free of charge, to any person obtaining a copy
14
+ of this software and associated documentation files (the "Software"), to deal
15
+ in the Software without restriction, including without limitation the rights
16
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17
+ copies of the Software, and to permit persons to whom the Software is
18
+ furnished to do so, subject to the following conditions:
19
+
20
+ The above copyright notice and this permission notice shall be included in all
21
+ copies or substantial portions of the Software.
22
+
23
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29
+ SOFTWARE.
30
+ License-File: LICENSE
31
+ Keywords: admin,asgi,htmx,litestar,sqlalchemy
32
+ Classifier: Development Status :: 3 - Alpha
33
+ Classifier: Framework :: AsyncIO
34
+ Classifier: Intended Audience :: Developers
35
+ Classifier: License :: OSI Approved :: MIT License
36
+ Classifier: Programming Language :: Python :: 3
37
+ Classifier: Programming Language :: Python :: 3.10
38
+ Classifier: Programming Language :: Python :: 3.11
39
+ Classifier: Programming Language :: Python :: 3.12
40
+ Classifier: Programming Language :: Python :: 3.13
41
+ Classifier: Programming Language :: Python :: 3.14
42
+ Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
43
+ Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
44
+ Classifier: Typing :: Typed
45
+ Requires-Python: >=3.10
46
+ Requires-Dist: jinja2>=3.1
47
+ Requires-Dist: litestar>=2.24.0
48
+ Requires-Dist: sqlalchemy>=2.0
49
+ Description-Content-Type: text/markdown
50
+
51
+ # admin-litestar
52
+
53
+ A server-rendered admin panel for [Litestar](https://litestar.dev) applications backed by
54
+ SQLAlchemy. No build step, no JavaScript toolchain, no CDN at runtime — the CSS is
55
+ hand-written and HTMX is vendored as package data.
56
+
57
+ It knows SQLAlchemy and Litestar. It knows nothing about your schema, your authentication,
58
+ or where you keep your audit trail — those arrive through protocols you implement. A test
59
+ in this repository fails if the package ever imports a host application.
60
+
61
+ ## Install
62
+
63
+ ```bash
64
+ uv add admin-litestar
65
+ ```
66
+
67
+ Requires Python 3.10+. Depends only on `litestar`, `sqlalchemy` and `jinja2`.
68
+
69
+ ## Usage
70
+
71
+ Declare a `ModelSpec` per model, implement three small protocols, and mount what `Admin`
72
+ gives you.
73
+
74
+ ```python
75
+ from litestar import Litestar
76
+ from litestar.stores.memory import MemoryStore
77
+
78
+ from admin_litestar import (
79
+ Admin,
80
+ AdminConfig,
81
+ DETAIL,
82
+ EXPORT,
83
+ LIST,
84
+ ModelSpec,
85
+ hash_password,
86
+ verify_password,
87
+ )
88
+
89
+ INVOICE = ModelSpec(
90
+ model=Invoice,
91
+ slug="invoice",
92
+ label="Invoices",
93
+ group="Billing",
94
+ list_columns=("id", "reference", "issued_at"),
95
+ detail_columns=("id", "reference", "note", "issued_at"),
96
+ capabilities=frozenset({LIST, DETAIL, EXPORT}),
97
+ order_by="id",
98
+ searchable=("reference",),
99
+ )
100
+
101
+
102
+ class Auth:
103
+ """Decides who may enter the admin, and whether they still may."""
104
+
105
+ async def authenticate(self, session, username, password):
106
+ user = await lookup(session, username)
107
+ if user and verify_password(password, user.admin_password):
108
+ return user
109
+ return None
110
+
111
+ def identity_of(self, user):
112
+ return user.id
113
+
114
+ async def is_valid(self, session, actor_id) -> bool:
115
+ return await still_permitted(session, actor_id)
116
+
117
+
118
+ admin = Admin(
119
+ config=AdminConfig(path="/admin", static_path="/admin-static"),
120
+ specs=[INVOICE],
121
+ auth=Auth(),
122
+ audit=YourAuditSink(),
123
+ cache=lambda request: your_cache,
124
+ session_factory=async_sessionmaker(engine),
125
+ )
126
+
127
+ app = Litestar(
128
+ route_handlers=[admin.router(), admin.static_router()],
129
+ template_config=admin.template_config(),
130
+ middleware=[admin.session_config(MemoryStore()).middleware],
131
+ csrf_config=admin.csrf_config(SECRET),
132
+ )
133
+ ```
134
+
135
+ That yields a login page, a gated sidebar shell, and list / detail / delete / CSV-export
136
+ routes for every spec that declares the matching capability.
137
+
138
+ ## The column boundary
139
+
140
+ `ModelSpec` distinguishes three kinds of column, and the distinction is enforced where
141
+ statements are built rather than where values are rendered:
142
+
143
+ | Field | Behaviour |
144
+ |---|---|
145
+ | `list_columns` | Loaded and shown in list views |
146
+ | `detail_columns` | Loaded and shown in detail views |
147
+ | `hidden_columns` | Permitted in detail views, **never** loaded by a list query |
148
+ | `excluded_columns` | Never selected, rendered or exported, anywhere |
149
+
150
+ List queries use `load_only()` over `list_columns`, so a hidden column is absent from the
151
+ SQL itself. That matters when a column's SQLAlchemy type decrypts on load: a list page
152
+ neither pays the cost nor can leak the value, even if a template is wrong. `ModelSpec`
153
+ rejects contradictory declarations at construction, so a hidden column named in
154
+ `list_columns` — or an excluded column named as searchable or filterable — is an error you
155
+ get at import time, not a leak you find later.
156
+
157
+ ## Search
158
+
159
+ `searchable` columns match with `ILIKE`. `exact_searchable` columns match by equality, and
160
+ `search_transform` is applied to the term first — which is how you search a keyed-digest
161
+ column without this package knowing anything about your hashing:
162
+
163
+ ```python
164
+ ModelSpec(
165
+ ...,
166
+ exact_searchable=("iin_digest",),
167
+ search_transform=your_digest_function,
168
+ )
169
+ ```
170
+
171
+ Exact search takes precedence when both are declared, because a digest cannot be matched
172
+ partially.
173
+
174
+ ## Pagination
175
+
176
+ Keyset, never `OFFSET`. The cursor is the last row's `order_by` value, coerced to the
177
+ column's Python type — dates and datetimes are parsed with `fromisoformat`. A malformed or
178
+ timezone-naive cursor is treated as absent and yields an unpaginated first page rather than
179
+ an error, because cursors arrive from URLs and URLs get edited.
180
+
181
+ ## Custom pages
182
+
183
+ Generic tables cannot do everything. `CustomPage` lets a host contribute its own routes,
184
+ rendered inside the same shell and listed in the same nav:
185
+
186
+ ```python
187
+ from admin_litestar import CustomPage
188
+
189
+ dashboard = CustomPage(
190
+ slug="dashboard", label="Dashboard", group="Overview", handlers=[DashboardController]
191
+ )
192
+ ```
193
+
194
+ Host templates take precedence over the package's, so `AdminConfig(template_dirs=(...))`
195
+ lets you override any template by name while extending `base.html`.
196
+
197
+ ## Authentication
198
+
199
+ The package owns the mechanism; you own the policy.
200
+
201
+ - `hash_password` / `verify_password` use `hashlib.scrypt` with `n=16384, r=8, p=1,
202
+ dklen=32`. The encoding is 86 characters, so it fits a `String(128)` column. Anything not
203
+ in that format fails verification — there is no fallback to another scheme.
204
+ - Login failures are counted per username **and** client IP, locking after 5 attempts for
205
+ 15 minutes. Deliberately separate from any lockout counter on your own user rows, so
206
+ admin brute-force cannot lock someone out of your main application.
207
+ - Sessions are server-side over a store you supply. `AuthBackend.is_valid` is re-checked on
208
+ every request, cached briefly, so revoking access takes effect in seconds rather than at
209
+ session expiry.
210
+ - CSRF protection covers every mutating route. Templates call `{{ csrf_token() }}`.
211
+
212
+ ## Design
213
+
214
+ Dark, near-monochrome, one amber accent, monospace for identifiers — chosen because admin
215
+ data is mostly ids, hashes, addresses and timestamps, which align and scan far better in a
216
+ monospaced column. Light and dark both ship, honouring `prefers-color-scheme` with an
217
+ explicit `data-theme` override.
218
+
219
+ `ModelSpec` validates at construction: unknown column names, a hidden column listed in
220
+ `list_columns`, an excluded column named as searchable, or an unknown capability all raise
221
+ immediately rather than producing an admin that quietly misbehaves.
222
+
223
+ ## Status
224
+
225
+ Early. The API has one real consumer, so every protocol here is a considered guess about
226
+ the second one. Expect `0.x` releases to move interfaces, and pin exactly if that matters.
227
+
228
+ `admin_litestar.__all__` is the compatibility promise. Deeper import paths work but carry
229
+ none — see [ARCHITECTURE.md](ARCHITECTURE.md).
230
+
231
+ ## Documentation
232
+
233
+ - [ARCHITECTURE.md](ARCHITECTURE.md) — layout, layering, where each guarantee is enforced
234
+ - [CHANGELOG.md](CHANGELOG.md) — what moved and when
235
+ - [RELEASING.md](RELEASING.md) — how a release is cut
236
+
237
+ ## Licence
238
+
239
+ MIT. See [LICENSE](LICENSE).
@@ -0,0 +1,189 @@
1
+ # admin-litestar
2
+
3
+ A server-rendered admin panel for [Litestar](https://litestar.dev) applications backed by
4
+ SQLAlchemy. No build step, no JavaScript toolchain, no CDN at runtime — the CSS is
5
+ hand-written and HTMX is vendored as package data.
6
+
7
+ It knows SQLAlchemy and Litestar. It knows nothing about your schema, your authentication,
8
+ or where you keep your audit trail — those arrive through protocols you implement. A test
9
+ in this repository fails if the package ever imports a host application.
10
+
11
+ ## Install
12
+
13
+ ```bash
14
+ uv add admin-litestar
15
+ ```
16
+
17
+ Requires Python 3.10+. Depends only on `litestar`, `sqlalchemy` and `jinja2`.
18
+
19
+ ## Usage
20
+
21
+ Declare a `ModelSpec` per model, implement three small protocols, and mount what `Admin`
22
+ gives you.
23
+
24
+ ```python
25
+ from litestar import Litestar
26
+ from litestar.stores.memory import MemoryStore
27
+
28
+ from admin_litestar import (
29
+ Admin,
30
+ AdminConfig,
31
+ DETAIL,
32
+ EXPORT,
33
+ LIST,
34
+ ModelSpec,
35
+ hash_password,
36
+ verify_password,
37
+ )
38
+
39
+ INVOICE = ModelSpec(
40
+ model=Invoice,
41
+ slug="invoice",
42
+ label="Invoices",
43
+ group="Billing",
44
+ list_columns=("id", "reference", "issued_at"),
45
+ detail_columns=("id", "reference", "note", "issued_at"),
46
+ capabilities=frozenset({LIST, DETAIL, EXPORT}),
47
+ order_by="id",
48
+ searchable=("reference",),
49
+ )
50
+
51
+
52
+ class Auth:
53
+ """Decides who may enter the admin, and whether they still may."""
54
+
55
+ async def authenticate(self, session, username, password):
56
+ user = await lookup(session, username)
57
+ if user and verify_password(password, user.admin_password):
58
+ return user
59
+ return None
60
+
61
+ def identity_of(self, user):
62
+ return user.id
63
+
64
+ async def is_valid(self, session, actor_id) -> bool:
65
+ return await still_permitted(session, actor_id)
66
+
67
+
68
+ admin = Admin(
69
+ config=AdminConfig(path="/admin", static_path="/admin-static"),
70
+ specs=[INVOICE],
71
+ auth=Auth(),
72
+ audit=YourAuditSink(),
73
+ cache=lambda request: your_cache,
74
+ session_factory=async_sessionmaker(engine),
75
+ )
76
+
77
+ app = Litestar(
78
+ route_handlers=[admin.router(), admin.static_router()],
79
+ template_config=admin.template_config(),
80
+ middleware=[admin.session_config(MemoryStore()).middleware],
81
+ csrf_config=admin.csrf_config(SECRET),
82
+ )
83
+ ```
84
+
85
+ That yields a login page, a gated sidebar shell, and list / detail / delete / CSV-export
86
+ routes for every spec that declares the matching capability.
87
+
88
+ ## The column boundary
89
+
90
+ `ModelSpec` distinguishes three kinds of column, and the distinction is enforced where
91
+ statements are built rather than where values are rendered:
92
+
93
+ | Field | Behaviour |
94
+ |---|---|
95
+ | `list_columns` | Loaded and shown in list views |
96
+ | `detail_columns` | Loaded and shown in detail views |
97
+ | `hidden_columns` | Permitted in detail views, **never** loaded by a list query |
98
+ | `excluded_columns` | Never selected, rendered or exported, anywhere |
99
+
100
+ List queries use `load_only()` over `list_columns`, so a hidden column is absent from the
101
+ SQL itself. That matters when a column's SQLAlchemy type decrypts on load: a list page
102
+ neither pays the cost nor can leak the value, even if a template is wrong. `ModelSpec`
103
+ rejects contradictory declarations at construction, so a hidden column named in
104
+ `list_columns` — or an excluded column named as searchable or filterable — is an error you
105
+ get at import time, not a leak you find later.
106
+
107
+ ## Search
108
+
109
+ `searchable` columns match with `ILIKE`. `exact_searchable` columns match by equality, and
110
+ `search_transform` is applied to the term first — which is how you search a keyed-digest
111
+ column without this package knowing anything about your hashing:
112
+
113
+ ```python
114
+ ModelSpec(
115
+ ...,
116
+ exact_searchable=("iin_digest",),
117
+ search_transform=your_digest_function,
118
+ )
119
+ ```
120
+
121
+ Exact search takes precedence when both are declared, because a digest cannot be matched
122
+ partially.
123
+
124
+ ## Pagination
125
+
126
+ Keyset, never `OFFSET`. The cursor is the last row's `order_by` value, coerced to the
127
+ column's Python type — dates and datetimes are parsed with `fromisoformat`. A malformed or
128
+ timezone-naive cursor is treated as absent and yields an unpaginated first page rather than
129
+ an error, because cursors arrive from URLs and URLs get edited.
130
+
131
+ ## Custom pages
132
+
133
+ Generic tables cannot do everything. `CustomPage` lets a host contribute its own routes,
134
+ rendered inside the same shell and listed in the same nav:
135
+
136
+ ```python
137
+ from admin_litestar import CustomPage
138
+
139
+ dashboard = CustomPage(
140
+ slug="dashboard", label="Dashboard", group="Overview", handlers=[DashboardController]
141
+ )
142
+ ```
143
+
144
+ Host templates take precedence over the package's, so `AdminConfig(template_dirs=(...))`
145
+ lets you override any template by name while extending `base.html`.
146
+
147
+ ## Authentication
148
+
149
+ The package owns the mechanism; you own the policy.
150
+
151
+ - `hash_password` / `verify_password` use `hashlib.scrypt` with `n=16384, r=8, p=1,
152
+ dklen=32`. The encoding is 86 characters, so it fits a `String(128)` column. Anything not
153
+ in that format fails verification — there is no fallback to another scheme.
154
+ - Login failures are counted per username **and** client IP, locking after 5 attempts for
155
+ 15 minutes. Deliberately separate from any lockout counter on your own user rows, so
156
+ admin brute-force cannot lock someone out of your main application.
157
+ - Sessions are server-side over a store you supply. `AuthBackend.is_valid` is re-checked on
158
+ every request, cached briefly, so revoking access takes effect in seconds rather than at
159
+ session expiry.
160
+ - CSRF protection covers every mutating route. Templates call `{{ csrf_token() }}`.
161
+
162
+ ## Design
163
+
164
+ Dark, near-monochrome, one amber accent, monospace for identifiers — chosen because admin
165
+ data is mostly ids, hashes, addresses and timestamps, which align and scan far better in a
166
+ monospaced column. Light and dark both ship, honouring `prefers-color-scheme` with an
167
+ explicit `data-theme` override.
168
+
169
+ `ModelSpec` validates at construction: unknown column names, a hidden column listed in
170
+ `list_columns`, an excluded column named as searchable, or an unknown capability all raise
171
+ immediately rather than producing an admin that quietly misbehaves.
172
+
173
+ ## Status
174
+
175
+ Early. The API has one real consumer, so every protocol here is a considered guess about
176
+ the second one. Expect `0.x` releases to move interfaces, and pin exactly if that matters.
177
+
178
+ `admin_litestar.__all__` is the compatibility promise. Deeper import paths work but carry
179
+ none — see [ARCHITECTURE.md](ARCHITECTURE.md).
180
+
181
+ ## Documentation
182
+
183
+ - [ARCHITECTURE.md](ARCHITECTURE.md) — layout, layering, where each guarantee is enforced
184
+ - [CHANGELOG.md](CHANGELOG.md) — what moved and when
185
+ - [RELEASING.md](RELEASING.md) — how a release is cut
186
+
187
+ ## Licence
188
+
189
+ MIT. See [LICENSE](LICENSE).
@@ -0,0 +1,54 @@
1
+ [project]
2
+ name = "admin-litestar"
3
+ version = "0.1.0"
4
+ description = "Server-rendered admin for Litestar + SQLAlchemy applications"
5
+ readme = "README.md"
6
+ requires-python = ">=3.10"
7
+ license = { file = "LICENSE" }
8
+ authors = [{ name = "Omirtay Adilkhan", email = "aomertayevich@gmail.com" }]
9
+ keywords = ["litestar", "sqlalchemy", "admin", "htmx", "asgi"]
10
+ classifiers = [
11
+ "Development Status :: 3 - Alpha",
12
+ "Framework :: AsyncIO",
13
+ "Intended Audience :: Developers",
14
+ "License :: OSI Approved :: MIT License",
15
+ "Programming Language :: Python :: 3",
16
+ "Programming Language :: Python :: 3.10",
17
+ "Programming Language :: Python :: 3.11",
18
+ "Programming Language :: Python :: 3.12",
19
+ "Programming Language :: Python :: 3.13",
20
+ "Programming Language :: Python :: 3.14",
21
+ "Topic :: Internet :: WWW/HTTP :: Dynamic Content",
22
+ "Topic :: Software Development :: Libraries :: Application Frameworks",
23
+ "Typing :: Typed",
24
+ ]
25
+ dependencies = [
26
+ "litestar>=2.24.0",
27
+ "sqlalchemy>=2.0",
28
+ "jinja2>=3.1",
29
+ ]
30
+
31
+ [project.urls]
32
+ Homepage = "https://github.com/adllkhan/admin-litestar"
33
+ Repository = "https://github.com/adllkhan/admin-litestar"
34
+ Issues = "https://github.com/adllkhan/admin-litestar/issues"
35
+
36
+ [dependency-groups]
37
+ dev = [
38
+ "pytest>=9.1.1",
39
+ "pytest-asyncio>=1.4.0",
40
+ ]
41
+
42
+ [build-system]
43
+ requires = ["hatchling"]
44
+ build-backend = "hatchling.build"
45
+
46
+ [tool.hatch.build.targets.wheel]
47
+ packages = ["src/admin_litestar"]
48
+
49
+ [tool.hatch.build.targets.sdist]
50
+ include = ["src", "tests", "README.md", "LICENSE", "pyproject.toml"]
51
+
52
+ [tool.pytest.ini_options]
53
+ testpaths = ["tests"]
54
+ asyncio_mode = "auto"
@@ -0,0 +1,50 @@
1
+ """Server-rendered admin for Litestar + SQLAlchemy applications.
2
+
3
+ Everything a host application needs is exported here. Deeper import paths such
4
+ as ``admin_litestar.queries`` happen to work, but the compatibility promise is
5
+ this module's ``__all__`` and nothing below it — see ARCHITECTURE.md.
6
+ """
7
+
8
+ from importlib.metadata import PackageNotFoundError, version
9
+
10
+ from .admin import Admin, AdminConfig
11
+ from .auth import actor_of
12
+ from .constants import CAPABILITIES, DELETE, DETAIL, EXPORT, LIST
13
+ from .export import csv_rows
14
+ from .pages import CustomPage
15
+ from .passwords import hash_password, verify_password
16
+ from .protocols import AuditSink, AuthBackend, CacheBackend
17
+ from .queries import count_statement, detail_statement, list_statement
18
+ from .render import is_htmx, project, render_value
19
+ from .spec import ModelSpec
20
+
21
+ try:
22
+ __version__ = version("admin-litestar")
23
+ except PackageNotFoundError: # pragma: no cover - running from a source tree
24
+ __version__ = "0.0.0.dev0"
25
+
26
+ __all__ = [
27
+ "Admin",
28
+ "AdminConfig",
29
+ "AuditSink",
30
+ "AuthBackend",
31
+ "CAPABILITIES",
32
+ "CacheBackend",
33
+ "CustomPage",
34
+ "DELETE",
35
+ "DETAIL",
36
+ "EXPORT",
37
+ "LIST",
38
+ "ModelSpec",
39
+ "__version__",
40
+ "actor_of",
41
+ "count_statement",
42
+ "csv_rows",
43
+ "detail_statement",
44
+ "hash_password",
45
+ "is_htmx",
46
+ "list_statement",
47
+ "project",
48
+ "render_value",
49
+ "verify_password",
50
+ ]