fastapi-restly 0.5.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 (40) hide show
  1. fastapi_restly-0.5.0/LICENSE +21 -0
  2. fastapi_restly-0.5.0/MANIFEST.in +1 -0
  3. fastapi_restly-0.5.0/PKG-INFO +407 -0
  4. fastapi_restly-0.5.0/README.md +349 -0
  5. fastapi_restly-0.5.0/fastapi_restly/__init__.py +106 -0
  6. fastapi_restly-0.5.0/fastapi_restly/_exception_handlers.py +194 -0
  7. fastapi_restly-0.5.0/fastapi_restly/_pytest_fixtures.py +256 -0
  8. fastapi_restly-0.5.0/fastapi_restly/db/__init__.py +31 -0
  9. fastapi_restly-0.5.0/fastapi_restly/db/_globals.py +76 -0
  10. fastapi_restly-0.5.0/fastapi_restly/db/_proxy.py +42 -0
  11. fastapi_restly-0.5.0/fastapi_restly/db/_session.py +275 -0
  12. fastapi_restly-0.5.0/fastapi_restly/exceptions.py +12 -0
  13. fastapi_restly-0.5.0/fastapi_restly/models/__init__.py +18 -0
  14. fastapi_restly-0.5.0/fastapi_restly/models/_base.py +84 -0
  15. fastapi_restly-0.5.0/fastapi_restly/objects.py +144 -0
  16. fastapi_restly-0.5.0/fastapi_restly/py.typed +0 -0
  17. fastapi_restly-0.5.0/fastapi_restly/pytest_fixtures.py +24 -0
  18. fastapi_restly-0.5.0/fastapi_restly/query/__init__.py +13 -0
  19. fastapi_restly-0.5.0/fastapi_restly/query/_impl.py +594 -0
  20. fastapi_restly-0.5.0/fastapi_restly/query/_shared.py +22 -0
  21. fastapi_restly-0.5.0/fastapi_restly/schemas/__init__.py +29 -0
  22. fastapi_restly-0.5.0/fastapi_restly/schemas/_base.py +518 -0
  23. fastapi_restly-0.5.0/fastapi_restly/schemas/_generator.py +382 -0
  24. fastapi_restly-0.5.0/fastapi_restly/testing/__init__.py +20 -0
  25. fastapi_restly-0.5.0/fastapi_restly/testing/_client.py +98 -0
  26. fastapi_restly-0.5.0/fastapi_restly/testing/_fixtures.py +20 -0
  27. fastapi_restly-0.5.0/fastapi_restly/views/__init__.py +40 -0
  28. fastapi_restly-0.5.0/fastapi_restly/views/_async.py +216 -0
  29. fastapi_restly-0.5.0/fastapi_restly/views/_base.py +1294 -0
  30. fastapi_restly-0.5.0/fastapi_restly/views/_openapi.py +206 -0
  31. fastapi_restly-0.5.0/fastapi_restly/views/_react_admin.py +393 -0
  32. fastapi_restly-0.5.0/fastapi_restly/views/_sync.py +213 -0
  33. fastapi_restly-0.5.0/fastapi_restly.egg-info/PKG-INFO +407 -0
  34. fastapi_restly-0.5.0/fastapi_restly.egg-info/SOURCES.txt +38 -0
  35. fastapi_restly-0.5.0/fastapi_restly.egg-info/dependency_links.txt +1 -0
  36. fastapi_restly-0.5.0/fastapi_restly.egg-info/entry_points.txt +2 -0
  37. fastapi_restly-0.5.0/fastapi_restly.egg-info/requires.txt +30 -0
  38. fastapi_restly-0.5.0/fastapi_restly.egg-info/top_level.txt +1 -0
  39. fastapi_restly-0.5.0/pyproject.toml +134 -0
  40. fastapi_restly-0.5.0/setup.cfg +4 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Rutger Prins
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 @@
1
+ prune tests
@@ -0,0 +1,407 @@
1
+ Metadata-Version: 2.4
2
+ Name: fastapi-restly
3
+ Version: 0.5.0
4
+ Summary: A REST Framework for FastAPI
5
+ Author-email: Rutger Prins <rutgerprins@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/rjprins/fastapi-restly
8
+ Project-URL: Repository, https://github.com/rjprins/fastapi-restly
9
+ Project-URL: Source, https://github.com/rjprins/fastapi-restly
10
+ Project-URL: Documentation, https://rjprins.github.io/fastapi-restly/
11
+ Project-URL: Issues, https://github.com/rjprins/fastapi-restly/issues
12
+ Project-URL: Changelog, https://github.com/rjprins/fastapi-restly/blob/main/CHANGELOG.md
13
+ Keywords: fastapi,rest,crud,crud-api,sqlalchemy,pydantic,api,framework,react-admin
14
+ Classifier: Development Status :: 4 - Beta
15
+ Classifier: Framework :: FastAPI
16
+ Classifier: Intended Audience :: Developers
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Programming Language :: Python :: 3.14
23
+ Classifier: Topic :: Database
24
+ Classifier: Topic :: Internet :: WWW/HTTP :: HTTP Servers
25
+ Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
26
+ Classifier: Typing :: Typed
27
+ Requires-Python: >=3.10
28
+ Description-Content-Type: text/markdown
29
+ License-File: LICENSE
30
+ Requires-Dist: alembic>=1.15.2
31
+ Requires-Dist: fastapi>=0.115.0
32
+ Requires-Dist: orjson>=3.10.18
33
+ Requires-Dist: pydantic>=2.11.0
34
+ Requires-Dist: pydantic-settings>=2.9.1
35
+ Requires-Dist: sqlalchemy[asyncio]>=2.0.22
36
+ Provides-Extra: standard
37
+ Requires-Dist: aiosqlite>=0.21.0; extra == "standard"
38
+ Requires-Dist: fastapi[standard]>=0.115.0; extra == "standard"
39
+ Requires-Dist: httpx>=0.27.0; extra == "standard"
40
+ Requires-Dist: pytest>=8.3.5; extra == "standard"
41
+ Requires-Dist: pytest-asyncio>=0.24.0; extra == "standard"
42
+ Requires-Dist: pytest-cov>=6.1.1; extra == "standard"
43
+ Provides-Extra: docs
44
+ Requires-Dist: ghp-import>=2.1.0; extra == "docs"
45
+ Requires-Dist: myst-parser>=4.0.1; extra == "docs"
46
+ Requires-Dist: pydata-sphinx-theme>=0.16.0; extra == "docs"
47
+ Requires-Dist: sphinx>=8.1.3; extra == "docs"
48
+ Requires-Dist: sphinx-autobuild>=2024.10.3; extra == "docs"
49
+ Requires-Dist: sphinx-copybutton>=0.5.2; extra == "docs"
50
+ Requires-Dist: sphinx-design>=0.6.0; extra == "docs"
51
+ Requires-Dist: sphinx-sitemap>=2.9.0; extra == "docs"
52
+ Provides-Extra: testing
53
+ Requires-Dist: httpx>=0.27.0; extra == "testing"
54
+ Requires-Dist: pytest>=8.3.5; extra == "testing"
55
+ Requires-Dist: pytest-asyncio>=0.24.0; extra == "testing"
56
+ Requires-Dist: pytest-cov>=6.1.1; extra == "testing"
57
+ Dynamic: license-file
58
+
59
+ # FastAPI-Restly
60
+
61
+ [![CI](https://github.com/rjprins/fastapi-restly/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/rjprins/fastapi-restly/actions/workflows/ci.yml)
62
+ [![Python](https://img.shields.io/badge/python-3.10%20%7C%203.11%20%7C%203.12%20%7C%203.13-blue)](https://github.com/rjprins/fastapi-restly/blob/main/pyproject.toml)
63
+ [![License](https://img.shields.io/github/license/rjprins/fastapi-restly)](https://github.com/rjprins/fastapi-restly/blob/main/LICENSE)
64
+ [![Coverage](https://rjprins.github.io/fastapi-restly/coverage/badge.svg)](https://rjprins.github.io/fastapi-restly/coverage/)
65
+
66
+ <p align="center">
67
+ <img src="https://raw.githubusercontent.com/rjprins/fastapi-restly/main/docs/_static/restly-cat-white-bg.png" alt="FastAPI-Restly logo" width="200">
68
+ </p>
69
+
70
+ **Build maintainable REST APIs on FastAPI, SQLAlchemy 2.0, and Pydantic v2 — with real class-based views.**
71
+
72
+ > **Status:** `0.5.0` — first public beta release.
73
+ > ```bash
74
+ > pip install "fastapi-restly[standard]"
75
+ > ```
76
+
77
+ **Docs:** <https://rjprins.github.io/fastapi-restly/> · **[Changelog](CHANGELOG.md)** · **[Contributing](CONTRIBUTING.md)** · **[Security](SECURITY.md)** · **[Examples](example-projects/)**
78
+
79
+ ## Why FastAPI-Restly?
80
+
81
+ FastAPI-Restly is a small REST resource layer on top of FastAPI, SQLAlchemy, and Pydantic:
82
+
83
+ ```text
84
+ SQLAlchemy owns persistence.
85
+ Pydantic owns validation and serialization.
86
+ FastAPI owns routing and dependency injection.
87
+ Restly owns repetitive REST resource mechanics.
88
+ ```
89
+
90
+ The base `View` class is the foundation. It gives related endpoints a shared
91
+ prefix, tags, dependencies, and ordinary Python inheritance without forcing a
92
+ model or CRUD shape. `RestView` and `AsyncRestView` build on that foundation for
93
+ the common list/get/create/update/delete case.
94
+
95
+ - **True class-based views** — group endpoints on real Python classes with inheritance and method overrides.
96
+ - **REST endpoints in minutes** — use `View` for custom resources, or `AsyncRestView` / `RestView` for generated CRUD.
97
+ - **Class-level dependencies** — apply authentication, rate limits, tenant context, or other FastAPI dependencies once per view.
98
+ - **Explicit override points** — replace an endpoint, a business-logic handler, or an object helper without awkward hacks.
99
+ - **Modern stack** — SQLAlchemy 2.0, Pydantic v2, async and sync support.
100
+ - **Filtering, pagination, sorting** — standard HTTP query interface generated from your response schema.
101
+ - **Field control** — `ReadOnly` / `WriteOnly` markers, plus scalar foreign-key references via `IDRef[...]`.
102
+ - **React Admin ready** — `AsyncReactAdminView` speaks the `ra-data-simple-rest` wire contract, no custom data provider needed.
103
+ - **Testing utilities** — `RestlyTestClient` and savepoint-based isolation fixtures.
104
+
105
+ ## Quickstart
106
+
107
+ FastAPI-Restly turns a SQLAlchemy model into a class-based CRUD resource:
108
+
109
+ ```python
110
+ import fastapi_restly as fr
111
+ from fastapi import FastAPI
112
+ from sqlalchemy.orm import Mapped
113
+
114
+ app = FastAPI()
115
+
116
+ class User(fr.IDBase):
117
+ name: Mapped[str]
118
+ email: Mapped[str]
119
+
120
+ @fr.include_view(app)
121
+ class UserView(fr.AsyncRestView):
122
+ prefix = "/users"
123
+ model = User
124
+ ```
125
+
126
+ That view exposes list, create, read, patch, and delete endpoints with filtering,
127
+ sorting, pagination, and an auto-generated Pydantic schema. For the full
128
+ copy-paste app, database setup, and run command, see
129
+ [Getting Started](docs/getting_started.md).
130
+
131
+ For endpoints that are related but not CRUD, start with `View`:
132
+
133
+ ```python
134
+ from typing import Annotated
135
+ from fastapi import Depends
136
+
137
+ def get_current_user():
138
+ ...
139
+
140
+ @fr.include_view(app)
141
+ class AccountView(fr.View):
142
+ prefix = "/account"
143
+ tags = ["account"]
144
+
145
+ current_user: Annotated[User, Depends(get_current_user)]
146
+
147
+ @fr.get("/me")
148
+ async def me(self) -> AccountRead:
149
+ return AccountRead.from_user(self.current_user)
150
+
151
+ @fr.post("/password")
152
+ async def change_password(self, payload: PasswordChange) -> AccountRead:
153
+ ...
154
+ ```
155
+
156
+ Annotated dependencies become instance attributes, so shared request context
157
+ lives on the view class instead of being repeated on every endpoint. The same
158
+ pattern works on `RestView` / `AsyncRestView`.
159
+
160
+ ## Philosophy
161
+
162
+ Restly uses a layered approach. Each layer adds convenience while letting you
163
+ drop down for deeper control. The less customization you need, the more you get
164
+ out-of-the-box — full customization never requires awkward hacks. Restly stays
165
+ close to patterns already provided by FastAPI, Pydantic, and SQLAlchemy.
166
+
167
+ ## Installation (development)
168
+
169
+ ```bash
170
+ git clone https://github.com/rjprins/fastapi-restly.git
171
+ cd fastapi-restly
172
+ uv sync
173
+ ```
174
+
175
+ ## Advanced features
176
+
177
+ ### Manual schema definition
178
+
179
+ For custom validation, aliases, or stable public contracts, define an explicit
180
+ read schema:
181
+
182
+ ```python
183
+ from datetime import datetime
184
+
185
+ class UserRead(fr.IDSchema):
186
+ name: str
187
+ email: str
188
+ created_at: fr.ReadOnly[datetime]
189
+
190
+ @fr.include_view(app)
191
+ class UserView(fr.AsyncRestView):
192
+ prefix = "/users"
193
+ model = User
194
+ schema = UserRead
195
+ ```
196
+
197
+ Restly derives create and update schemas from `UserRead` by default. When you
198
+ need full control over write payloads, declare them explicitly:
199
+
200
+ ```python
201
+ class UserCreate(fr.BaseSchema):
202
+ name: str
203
+ email: str
204
+
205
+ class UserUpdate(fr.BaseSchema):
206
+ name: str | None = None
207
+ email: str | None = None
208
+
209
+ @fr.include_view(app)
210
+ class UserView(fr.AsyncRestView):
211
+ prefix = "/users"
212
+ model = User
213
+ schema = UserRead
214
+ creation_schema = UserCreate
215
+ update_schema = UserUpdate
216
+ ```
217
+
218
+ Use **auto-schema** for prototypes and internal tools. Use an **explicit schema** when contract stability and validation control matter (public APIs, aliases, strict response shapes).
219
+
220
+ ### List endpoint query parameters
221
+
222
+ List endpoints expose a stable URL parameter dialect generated from the
223
+ response schema:
224
+
225
+ ```bash
226
+ GET /users/?name=John&age__gte=21
227
+ GET /users/?status=active,pending # comma-separated → OR (IN)
228
+ GET /users/?status__ne=archived,deleted # comma-separated → NOT IN
229
+ GET /users/?email__icontains=example
230
+ GET /users/?deleted_at__isnull=true
231
+ GET /users/?sort=-created_at,name
232
+ GET /users/?page=2&page_size=10
233
+ ```
234
+
235
+ Parameter keys follow the **response schema's public names** end-to-end —
236
+ including dotted relation paths. If `ArticleRead.author` has
237
+ `Field(alias="writer")` and `AuthorRead.name` has
238
+ `Field(alias="authorName")`, the URL key is `writer.authorName`. Aliased
239
+ fields are only reachable by their alias; `populate_by_name` does not
240
+ extend the URL surface with the Python field name.
241
+
242
+ Pagination is opt-in: omitting `page_size` returns every matching row.
243
+ For public/production endpoints set `default_page_size` and
244
+ `max_page_size` on the view class:
245
+
246
+ ```python
247
+ class UserView(fr.AsyncRestView):
248
+ default_page_size = 25
249
+ max_page_size = 200
250
+ ```
251
+
252
+ See [How-To: Filter, Sort, and Paginate Lists](docs/howto_query_modifiers.md)
253
+ for the full operator surface, alias rules, and pagination guidance.
254
+
255
+ ### Read-only and write-only fields
256
+
257
+ `IDSchema` already provides a read-only `id`, so don't redeclare it unless you need to narrow the type.
258
+
259
+ ```python
260
+ class UserRead(fr.IDSchema):
261
+ name: str
262
+ email: str
263
+ password: fr.WriteOnly[str] # stripped by to_response_schema()
264
+ created_at: fr.ReadOnly[datetime] # cannot be set in requests
265
+ ```
266
+
267
+ ### Relationships
268
+
269
+ ```python
270
+ from sqlalchemy import ForeignKey
271
+ from sqlalchemy.orm import Mapped, mapped_column
272
+
273
+ class Order(fr.IDBase):
274
+ customer_id: Mapped[int] = mapped_column(ForeignKey("customer.id"))
275
+ total: Mapped[float]
276
+
277
+ class OrderRead(fr.IDSchema):
278
+ customer_id: fr.IDRef[Customer] # wire format: 123 — resolved to FK
279
+ total: float
280
+ ```
281
+
282
+ ### Custom endpoints and handlers
283
+
284
+ Add endpoints with `@fr.get`, `@fr.post`, `@fr.put`, `@fr.patch`, `@fr.delete`, or the generic `@fr.route`. Override `perform_*` handlers (`perform_listing`, `perform_get`, `perform_create`, ...) to customise built-in CRUD logic without replacing the endpoint.
285
+
286
+ ```python
287
+ @fr.include_view(app)
288
+ class UserView(fr.AsyncRestView):
289
+ prefix = "/users"
290
+ model = User
291
+ schema = UserRead
292
+
293
+ @fr.get("/{id}/download")
294
+ async def download_user(self, id: int):
295
+ return {"id": id, "status": "ok"}
296
+
297
+ async def perform_listing(self, query_params):
298
+ # Custom logic here
299
+ return await super().perform_listing(query_params)
300
+ ```
301
+
302
+ ### React Admin integration
303
+
304
+ Use `AsyncReactAdminView` to get a backend that [react-admin](https://marmelab.com/react-admin/) with [`ra-data-simple-rest`](https://github.com/marmelab/react-admin/tree/master/packages/ra-data-simple-rest) connects to out of the box:
305
+
306
+ ```python
307
+ @fr.include_view(app)
308
+ class ProductView(fr.AsyncReactAdminView):
309
+ prefix = "/products"
310
+ model = Product
311
+ schema = ProductRead
312
+ ```
313
+
314
+ The view speaks the `ra-data-simple-rest` wire contract:
315
+
316
+ - **List** — translates `sort=["name","ASC"]`, `range=[0,24]`, and `filter={"name":"foo"}` into SQL and returns a JSON array with a `Content-Range: items 0-24/315` header.
317
+ - **All other CRUD** — `GET /{id}`, `POST /`, `PATCH /{id}`, `DELETE /{id}` work unchanged.
318
+
319
+ See [React Admin Integration](https://rjprins.github.io/fastapi-restly/howto_react_admin.html) in the docs for CORS setup and customization.
320
+
321
+ ### Excluding built-in routes
322
+
323
+ ```python
324
+ @fr.include_view(app)
325
+ class UserView(fr.AsyncRestView):
326
+ prefix = "/users"
327
+ model = User
328
+ exclude_routes = (fr.ViewRoute.DELETE,)
329
+ ```
330
+
331
+ ### Pagination metadata
332
+
333
+ ```python
334
+ @fr.include_view(app)
335
+ class UserView(fr.AsyncRestView):
336
+ prefix = "/users"
337
+ model = User
338
+ include_pagination_metadata = True
339
+ # Response: {"items": [...], "total": N, "page": 1, "page_size": 100, "total_pages": N, ...}
340
+ ```
341
+
342
+ ## Testing
343
+
344
+ `fastapi_restly.pytest_fixtures` provides namespaced pytest fixtures (`restly_app`, `restly_client`, `restly_async_session`, `restly_session`) for test clients and **savepoint-based isolation**. The testing extra installs a pytest plugin entry point, so pytest auto-loads these fixtures.
345
+
346
+ Install the testing extra when consuming FastAPI-Restly as a package:
347
+
348
+ ```bash
349
+ pip install "fastapi-restly[testing]"
350
+ ```
351
+
352
+ Configure Restly for your test database in `conftest.py`.
353
+
354
+ `RestlyTestClient` automatically asserts the expected HTTP status (`200` for GET, `201` for POST, `204` for DELETE, ...) and raises a descriptive `AssertionError` with the response body on failure:
355
+
356
+ ```python
357
+ # test_users.py
358
+ def test_create_and_fetch_user(restly_client):
359
+ # Raises AssertionError if status != 201
360
+ response = restly_client.post("/users/", json={"name": "John", "email": "john@example.com"})
361
+ user_id = response.json()["id"]
362
+
363
+ # Raises AssertionError if status != 200
364
+ data = restly_client.get(f"/users/{user_id}").json()
365
+ assert data["name"] == "John"
366
+ ```
367
+
368
+ Pass `assert_status_code=None` to skip the assertion and inspect the response yourself.
369
+
370
+ ## Configuration
371
+
372
+ ```python
373
+ # Async SQLite
374
+ fr.configure(async_database_url="sqlite+aiosqlite:///app.db")
375
+
376
+ # Async PostgreSQL
377
+ fr.configure(async_database_url="postgresql+asyncpg://user:pass@localhost/db")
378
+
379
+ # Sync SQLite
380
+ fr.configure(database_url="sqlite:///app.db")
381
+ ```
382
+
383
+ Restly has one public process-wide configuration. For per-view databases,
384
+ read replicas, or other custom session wiring, use a normal FastAPI dependency
385
+ on that view; see the existing-project how-to in the documentation.
386
+
387
+ ## Documentation
388
+
389
+ - **[Getting Started](https://rjprins.github.io/fastapi-restly/getting_started.html)** — fast path from zero to a working API
390
+ - **[User Guide](https://rjprins.github.io/fastapi-restly/user_guide.html)** — tutorial walkthroughs and topic guides
391
+ - **[API Reference](https://rjprins.github.io/fastapi-restly/api_reference.html)** — complete API docs
392
+
393
+ ## Examples
394
+
395
+ Complete applications under [`example-projects/`](example-projects/):
396
+
397
+ - **[Shop](example-projects/shop/)** — e-commerce API with products, orders, customers
398
+ - **[Blog](example-projects/blog/)** — minimal blog with a single `Blog` model
399
+ - **[SaaS](example-projects/saas/)** — multi-tenant project management API
400
+
401
+ ## Contributing
402
+
403
+ Pull requests and issue discussions welcome. See [CONTRIBUTING.md](CONTRIBUTING.md) for setup, coding standards, and the test workflow. Security issues: see [SECURITY.md](SECURITY.md).
404
+
405
+ ## License
406
+
407
+ MIT — see [LICENSE](LICENSE).