formaxapi 0.1.8__py3-none-any.whl

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 (41) hide show
  1. fastschema/__init__.py +19 -0
  2. fastschema/core/__init__.py +13 -0
  3. fastschema/core/field_config.py +49 -0
  4. fastschema/core/metaclass.py +96 -0
  5. fastschema/core/model_factory.py +189 -0
  6. fastschema/core/route_base.py +70 -0
  7. fastschema/core/route_decorator.py +70 -0
  8. fastschema/core/route_field.py +135 -0
  9. fastschema/core/self_derived.py +27 -0
  10. formaxapi/__init__.py +19 -0
  11. formaxapi/core/__init__.py +13 -0
  12. formaxapi/core/field_config.py +49 -0
  13. formaxapi/core/metaclass.py +96 -0
  14. formaxapi/core/model_factory.py +189 -0
  15. formaxapi/core/route_base.py +70 -0
  16. formaxapi/core/route_decorator.py +70 -0
  17. formaxapi/core/route_field.py +135 -0
  18. formaxapi/core/self_derived.py +27 -0
  19. formaxapi-0.1.8.dist-info/METADATA +880 -0
  20. formaxapi-0.1.8.dist-info/RECORD +41 -0
  21. formaxapi-0.1.8.dist-info/WHEEL +5 -0
  22. formaxapi-0.1.8.dist-info/licenses/LICENSE +201 -0
  23. formaxapi-0.1.8.dist-info/top_level.txt +1 -0
  24. modelrouter/__init__.py +19 -0
  25. modelrouter/core/__init__.py +13 -0
  26. modelrouter/core/field_config.py +49 -0
  27. modelrouter/core/metaclass.py +96 -0
  28. modelrouter/core/model_factory.py +189 -0
  29. modelrouter/core/route_base.py +70 -0
  30. modelrouter/core/route_decorator.py +70 -0
  31. modelrouter/core/route_field.py +135 -0
  32. modelrouter/core/self_derived.py +27 -0
  33. routex/__init__.py +19 -0
  34. routex/core/__init__.py +13 -0
  35. routex/core/field_config.py +49 -0
  36. routex/core/metaclass.py +96 -0
  37. routex/core/model_factory.py +189 -0
  38. routex/core/route_base.py +70 -0
  39. routex/core/route_decorator.py +70 -0
  40. routex/core/route_field.py +135 -0
  41. routex/core/self_derived.py +27 -0
@@ -0,0 +1,880 @@
1
+ Metadata-Version: 2.4
2
+ Name: formaxapi
3
+ Version: 0.1.8
4
+ Summary: Class-based routing with dynamic Pydantic model generation for FastAPI
5
+ Author: EXO
6
+ License: Apache-2.0
7
+ Project-URL: Homepage, https://github.com/siavashnouri/fastschema
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: License :: OSI Approved :: Apache Software License
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
16
+ Requires-Python: >=3.10
17
+ Description-Content-Type: text/markdown
18
+ License-File: LICENSE
19
+ Requires-Dist: fastapi>=0.100.0
20
+ Requires-Dist: pydantic>=2.0.0
21
+ Dynamic: author
22
+ Dynamic: classifier
23
+ Dynamic: description
24
+ Dynamic: description-content-type
25
+ Dynamic: license
26
+ Dynamic: license-file
27
+ Dynamic: project-url
28
+ Dynamic: requires-dist
29
+ Dynamic: requires-python
30
+ Dynamic: summary
31
+
32
+ <p align="center">
33
+ <img src="fastschema.png" alt="fastschema" width="400">
34
+ </p>
35
+
36
+ # fastschema
37
+
38
+ Class-based routing with dynamic Pydantic model generation for FastAPI.
39
+
40
+ [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
41
+ [![FastAPI](https://img.shields.io/badge/FastAPI-0.100+-009688.svg)](https://fastapi.tiangolo.com/)
42
+ [![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://www.apache.org/licenses/LICENSE-2.0)
43
+
44
+ ## Why fastschema?
45
+
46
+ Traditional FastAPI development scatters code across Pydantic models, route handlers, and router configuration. **fastschema** consolidates everything into a single class:
47
+
48
+ - **Routes inside the class** — defined with `@route` decorator
49
+ - **Typed input** — `UserRoute.schema("add")` auto-validates request body
50
+ - **Auto-discovered schemas** — no manual registration
51
+ - **SelfDerivedModel** — derive schemas from own fields for bulk operations
52
+ - **One-liner router** — `route_factory()` returns a FastAPI `APIRouter`
53
+ - **Works with any ORM** — Pydantic, Beanie, SQLAlchemy/SQLModel
54
+
55
+ ```bash
56
+ pip install fastschema
57
+ ```
58
+
59
+ ---
60
+
61
+ ## 30-Second Demo
62
+
63
+ ```python
64
+ from __future__ import annotations
65
+ from fastapi import FastAPI, Request
66
+ from fastschema import FieldConfig, RouteField, RouteBase, route, route_factory
67
+
68
+ app = FastAPI()
69
+
70
+ class Add(FieldConfig):
71
+ required = True
72
+
73
+ class Edit(FieldConfig):
74
+ default = None
75
+
76
+ class UserRoute(RouteBase):
77
+ name: str = RouteField(add=Add(), edit=Edit(), min_length=1, max_length=100)
78
+ email: str = RouteField(add=Add(), edit=Edit())
79
+
80
+ @classmethod
81
+ @route(path="/users", method="POST", status_code=201)
82
+ async def create_user(cls, request: Request, data: UserRoute.schema("add")):
83
+ return {"id": "1", "name": data.name}
84
+
85
+ @classmethod
86
+ @route(path="/users", method="GET")
87
+ async def get_users(cls, request: Request):
88
+ return {"users": []}
89
+
90
+ app.include_router(route_factory(UserRoute))
91
+ ```
92
+
93
+ **Note:** Always use `from __future__ import annotations` at the top of your file.
94
+
95
+ ---
96
+
97
+ ## Table of Contents
98
+
99
+ - [Installation](#installation)
100
+ - [Quick Start](#quick-start)
101
+ - [Core Concepts](#core-concepts)
102
+ - [FieldConfig](#1-fieldconfig---schema-type-configurations)
103
+ - [RouteField](#2-routefield---declarative-fields)
104
+ - [RouteBase](#3-routebase---base-class)
105
+ - [@route Decorator](#4-route-decorator)
106
+ - [route_factory](#5-route_factory)
107
+ - [Examples](#examples)
108
+ - [Raw Pydantic](#raw-pydantic)
109
+ - [Beanie (MongoDB)](#beanie-mongodb)
110
+ - [SQLModel (SQLAlchemy)](#sqlmodel-sqlalchemy)
111
+ - [Advanced Features](#advanced-features)
112
+ - [Chain](#chain---compose-functions)
113
+ - [SelfDerivedModel](#selfderivedmodel---bulk-operations)
114
+ - [ClassVar Fields](#classvar-fields)
115
+ - [from_schema()](#from_schema)
116
+ - [Validators](#validators)
117
+ - [Constraint Params](#constraint-params)
118
+ - [API Reference](#api-reference)
119
+ - [License](#license)
120
+
121
+ ---
122
+
123
+ ## Installation
124
+
125
+ ```bash
126
+ pip install fastschema
127
+ ```
128
+
129
+ **Optional dependencies:**
130
+
131
+ ```bash
132
+ # For Beanie (MongoDB)
133
+ pip install beanie motor
134
+
135
+ # For SQLModel (SQLAlchemy)
136
+ pip install sqlmodel aiosqlite
137
+ ```
138
+
139
+ ---
140
+
141
+ ## Quick Start
142
+
143
+ ### 1. Define Schema Configs
144
+
145
+ ```python
146
+ from fastschema import FieldConfig
147
+
148
+ class Add(FieldConfig):
149
+ required = True
150
+
151
+ class Edit(FieldConfig):
152
+ default = None
153
+
154
+ class Output(FieldConfig):
155
+ pass
156
+ ```
157
+
158
+ ### 2. Define Route Class
159
+
160
+ ```python
161
+ from fastschema import RouteField, RouteBase, route
162
+
163
+ class UserRoute(RouteBase):
164
+ name: str = RouteField(add=Add(), edit=Edit(), output=Output(), min_length=1)
165
+ email: str = RouteField(add=Add(), edit=Edit(), output=Output())
166
+
167
+ @classmethod
168
+ @route(path="/users", method="POST", status_code=201)
169
+ async def create_user(cls, request: Request, data: UserRoute.schema("add")):
170
+ # data.name: str (required, min_length=1)
171
+ # data.email: str (required)
172
+ return {"id": "1", "name": data.name}
173
+ ```
174
+
175
+ ### 3. Register Routes
176
+
177
+ ```python
178
+ from fastapi import FastAPI
179
+ from fastschema import route_factory
180
+
181
+ app = FastAPI()
182
+ app.include_router(route_factory(UserRoute))
183
+ ```
184
+
185
+ ---
186
+
187
+ ## Core Concepts
188
+
189
+ ### 1. FieldConfig - Schema Type Configurations
190
+
191
+ `FieldConfig` defines how a field behaves in different schema contexts:
192
+
193
+ ```python
194
+ from fastschema import FieldConfig
195
+
196
+ class Add(FieldConfig):
197
+ required = True
198
+
199
+ class Edit(FieldConfig):
200
+ default = None
201
+
202
+ class Filter(FieldConfig):
203
+ default = None
204
+
205
+ class Output(FieldConfig):
206
+ pass
207
+ ```
208
+
209
+ | Attribute | Type | Default | Description |
210
+ |-----------|------|---------|-------------|
211
+ | `required` | `bool` | `False` | No default — must be provided |
212
+ | `default` | `Any` | `_UNSET` | Default value |
213
+ | `default_factory` | `Callable` | `None` | Factory for mutable defaults |
214
+ | `alias` | `str` | `None` | Field alias |
215
+ | `description` | `str` | `None` | Field description |
216
+ | `type_override` | `type` | `None` | Override field type |
217
+ | `exclude` | `bool` | `False` | Exclude from this schema |
218
+ | `frozen` | `bool` | `False` | Immutable field |
219
+ | `apply_func` | `Callable` | `None` | Transform function |
220
+ | `before` | `bool` | `True` | Run apply_func before/after validators |
221
+ | `metadata` | `dict` | `None` | Extra kwargs for `pydantic.Field` |
222
+
223
+ ---
224
+
225
+ ### 2. RouteField - Declarative Fields
226
+
227
+ `RouteField` inherits from Pydantic's `FieldInfo`, so it works everywhere:
228
+
229
+ ```python
230
+ from fastschema import RouteField, FieldConfig
231
+
232
+ class Add(FieldConfig):
233
+ required = True
234
+
235
+ # In RouteBase — schema configs work
236
+ class UserRoute(RouteBase):
237
+ title: str = RouteField(
238
+ add=Add(),
239
+ edit=Edit(),
240
+ alias="product_title",
241
+ description="The product title",
242
+ min_length=1,
243
+ max_length=200,
244
+ )
245
+
246
+ # In BaseModel — Pydantic Field params work
247
+ from pydantic import BaseModel
248
+
249
+ class MyModel(BaseModel):
250
+ title: str = RouteField(alias="x", description="test", min_length=1)
251
+ ```
252
+
253
+ **All Pydantic Field params are supported:**
254
+
255
+ ```python
256
+ RouteField(
257
+ # Pydantic params
258
+ alias="x",
259
+ validation_alias="title",
260
+ description="Field description",
261
+ exclude=False,
262
+ frozen=False,
263
+ min_length=1,
264
+ max_length=100,
265
+ gt=0,
266
+ ge=0,
267
+ lt=100,
268
+ le=100,
269
+ pattern=r'^[A-Z]+$',
270
+ multiple_of=5,
271
+ json_schema_extra={"example": "hello"},
272
+ # Schema configs
273
+ add=Add(),
274
+ edit=Edit(),
275
+ output=Output(),
276
+ )
277
+ ```
278
+
279
+ ---
280
+
281
+ ### 3. RouteBase - Base Class
282
+
283
+ `RouteBase` provides schema generation and introspection:
284
+
285
+ ```python
286
+ class UserRoute(RouteBase):
287
+ name: str = RouteField(add=Add(), edit=Edit())
288
+
289
+ # Generate schemas
290
+ UserRoute.schema("add") # => name: str (required)
291
+ UserRoute.schema("edit") # => name: str | None (optional)
292
+
293
+ # Introspection
294
+ UserRoute.schema_types() # => ["add", "edit"]
295
+ UserRoute.schema_fields("add") # => ["name"]
296
+ UserRoute.all_fields() # => {"name": FieldInfo(...)}
297
+ UserRoute.field_names() # => ["name"]
298
+ ```
299
+
300
+ ---
301
+
302
+ ### 4. @route Decorator
303
+
304
+ Define endpoints inside the class:
305
+
306
+ ```python
307
+ from fastschema import route
308
+
309
+ class UserRoute(RouteBase):
310
+ @classmethod
311
+ @route(
312
+ path="/users",
313
+ method="POST",
314
+ name="create_user",
315
+ description="Create a new user",
316
+ status_code=201,
317
+ tags=["users"],
318
+ )
319
+ async def create_user(cls, request: Request, data: UserRoute.schema("add")):
320
+ return {"id": "1"}
321
+ ```
322
+
323
+ | Parameter | Type | Default | Description |
324
+ |-----------|------|---------|-------------|
325
+ | `path` | `str` | *required* | URL path |
326
+ | `method` | `str` | `"GET"` | HTTP method |
327
+ | `name` | `str` | `None` | Route name |
328
+ | `description` | `str` | `None` | Route description |
329
+ | `status_code` | `int` | `200` | Response status code |
330
+ | `tags` | `list[str]` | `None` | OpenAPI tags |
331
+
332
+ ---
333
+
334
+ ### 5. route_factory
335
+
336
+ Collect all routes into a FastAPI router:
337
+
338
+ ```python
339
+ from fastschema import route_factory
340
+
341
+ router = route_factory(UserRoute, ProductRoute)
342
+ app.include_router(router)
343
+ ```
344
+
345
+ ---
346
+
347
+ ## Examples
348
+
349
+ ### Raw Pydantic
350
+
351
+ Full example without any ORM:
352
+
353
+ ```python
354
+ from __future__ import annotations
355
+ from fastapi import FastAPI, Request
356
+ from fastschema import FieldConfig, RouteField, RouteBase, route, route_factory
357
+ from pydantic import BaseModel, field_validator
358
+
359
+ app = FastAPI()
360
+
361
+ # --- Configs ---
362
+
363
+ class Add(FieldConfig):
364
+ required = True
365
+
366
+ class Edit(FieldConfig):
367
+ default = None
368
+
369
+ class Output(FieldConfig):
370
+ pass
371
+
372
+ # --- Route Class ---
373
+
374
+ class UserRoute(RouteBase):
375
+ name: str = RouteField(
376
+ add=Add(),
377
+ edit=Edit(),
378
+ output=Output(),
379
+ min_length=1,
380
+ max_length=100,
381
+ )
382
+ email: str = RouteField(add=Add(), edit=Edit(), output=Output())
383
+ age: int = RouteField(add=Add(default=0), edit=Edit(), output=Output(), ge=0)
384
+
385
+ @classmethod
386
+ @route(path="/users", method="GET")
387
+ async def get_users(cls, request: Request):
388
+ return {"users": []}
389
+
390
+ @classmethod
391
+ @route(path="/users", method="POST", status_code=201)
392
+ async def create_user(cls, request: Request, data: UserRoute.schema("add")):
393
+ return {"id": "1", "name": data.name}
394
+
395
+ @classmethod
396
+ @route(path="/users/{user_id}", method="PUT")
397
+ async def update_user(cls, request: Request, user_id: str, data: UserRoute.schema("edit")):
398
+ return {"id": user_id}
399
+
400
+ @classmethod
401
+ @route(path="/users/{user_id}", method="DELETE")
402
+ async def delete_user(cls, request: Request, user_id: str):
403
+ return {"deleted": True}
404
+
405
+ app.include_router(route_factory(UserRoute))
406
+ ```
407
+
408
+ ---
409
+
410
+ ### Beanie (MongoDB)
411
+
412
+ Full example with Beanie ODM:
413
+
414
+ ```python
415
+ from __future__ import annotations
416
+ from fastapi import FastAPI, Request
417
+ from fastschema import FieldConfig, RouteField, RouteBase, route, route_factory
418
+ from beanie import Document, PydanticObjectId
419
+ from contextlib import asynccontextmanager
420
+ from pymongo import AsyncMongoClient
421
+ from beanie import init_beanie
422
+ from typing import ClassVar
423
+
424
+ app = FastAPI()
425
+
426
+ # --- Configs ---
427
+
428
+ class Add(FieldConfig):
429
+ required = True
430
+
431
+ class Edit(FieldConfig):
432
+ default = None
433
+
434
+ class Output(FieldConfig):
435
+ pass
436
+
437
+ class Get(FieldConfig):
438
+ default = None
439
+
440
+ # --- Document + Route Class ---
441
+
442
+ class UserRoute(RouteBase, Document):
443
+ name: str = RouteField(add=Add(), edit=Edit(), output=Output(), min_length=1)
444
+ email: str = RouteField(add=Add(), edit=Edit(), output=Output())
445
+
446
+ # ClassVar — not in DB, available for schema generation
447
+ token: ClassVar[str | None] = RouteField(get=Get(), add=Add(exclude=True))
448
+
449
+ class Settings:
450
+ name = "users"
451
+
452
+ @classmethod
453
+ @route(path="/users", method="GET")
454
+ async def get_users(cls, request: Request):
455
+ users = await cls.find_all().to_list()
456
+ return [{"id": str(u.id), "name": u.name} for u in users]
457
+
458
+ @classmethod
459
+ @route(path="/users", method="POST", status_code=201)
460
+ async def create_user(cls, request: Request, data: UserRoute.schema("add")):
461
+ user = cls.from_schema(data)
462
+ await user.insert()
463
+ return {"id": str(user.id), "name": user.name}
464
+
465
+ @classmethod
466
+ @route(path="/users/{user_id}", method="GET")
467
+ async def get_user(cls, request: Request, user_id: str):
468
+ user = await cls.get(PydanticObjectId(user_id))
469
+ if not user:
470
+ return {"error": "not found"}
471
+ return {"id": str(user.id), "name": user.name}
472
+
473
+ @classmethod
474
+ @route(path="/users/{user_id}", method="DELETE")
475
+ async def delete_user(cls, request: Request, user_id: str):
476
+ user = await cls.get(PydanticObjectId(user_id))
477
+ if user:
478
+ await user.delete()
479
+ return {"deleted": True}
480
+
481
+ # --- Lifespan ---
482
+
483
+ @asynccontextmanager
484
+ async def lifespan(app: FastAPI):
485
+ client = AsyncMongoClient("mongodb://localhost:27017")
486
+ await init_beanie(database=client["mydb"], document_models=[UserRoute])
487
+ yield
488
+ client.close()
489
+
490
+ app = FastAPI(lifespan=lifespan)
491
+ app.include_router(route_factory(UserRoute))
492
+ ```
493
+
494
+ ---
495
+
496
+ ### SQLModel (SQLAlchemy)
497
+
498
+ Full example with SQLModel:
499
+
500
+ ```python
501
+ from __future__ import annotations
502
+ from fastapi import FastAPI, Request
503
+ from fastschema import FieldConfig, RouteField, RouteBase, route, route_factory
504
+ from sqlmodel import SQLModel, Field, Session, create_engine
505
+ from typing import ClassVar
506
+
507
+ # --- Database ---
508
+
509
+ engine = create_engine("sqlite:///database.db")
510
+
511
+ # --- Configs ---
512
+
513
+ class Add(FieldConfig):
514
+ required = True
515
+
516
+ class Edit(FieldConfig):
517
+ default = None
518
+
519
+ class Output(FieldConfig):
520
+ pass
521
+
522
+ # --- Model + Route Class ---
523
+
524
+ class UserRoute(RouteBase, SQLModel, table=True):
525
+ __tablename__ = "users"
526
+
527
+ id: int | None = Field(default=None, primary_key=True)
528
+ name: str = RouteField(
529
+ add=Add(),
530
+ edit=Edit(),
531
+ output=Output(),
532
+ min_length=1,
533
+ max_length=100,
534
+ )
535
+ email: str = RouteField(add=Add(), edit=Edit(), output=Output())
536
+
537
+ # ClassVar — not in DB, available for schema generation
538
+ token: ClassVar[str | None] = RouteField(
539
+ get=Get(),
540
+ add=Add(exclude=True),
541
+ )
542
+
543
+ @classmethod
544
+ @route(path="/users", method="GET")
545
+ async def get_users(cls, request: Request):
546
+ with Session(engine) as session:
547
+ users = session.exec(select(cls)).all()
548
+ return [{"id": u.id, "name": u.name} for u in users]
549
+
550
+ @classmethod
551
+ @route(path="/users", method="POST", status_code=201)
552
+ async def create_user(cls, request: Request, data: UserRoute.schema("add")):
553
+ user = cls.from_schema(data)
554
+ with Session(engine) as session:
555
+ session.add(user)
556
+ session.commit()
557
+ session.refresh(user)
558
+ return {"id": user.id, "name": user.name}
559
+
560
+ @classmethod
561
+ @route(path="/users/{user_id}", method="GET")
562
+ async def get_user(cls, request: Request, user_id: int):
563
+ with Session(engine) as session:
564
+ user = session.get(cls, user_id)
565
+ if not user:
566
+ return {"error": "not found"}
567
+ return {"id": user.id, "name": user.name}
568
+
569
+ @classmethod
570
+ @route(path="/users/{user_id}", method="DELETE")
571
+ async def delete_user(cls, request: Request, user_id: int):
572
+ with Session(engine) as session:
573
+ user = session.get(cls, user_id)
574
+ if user:
575
+ session.delete(user)
576
+ session.commit()
577
+ return {"deleted": True}
578
+
579
+ # --- Create tables and app ---
580
+
581
+ SQLModel.metadata.create_all(engine)
582
+
583
+ app = FastAPI()
584
+ app.include_router(route_factory(UserRoute))
585
+ ```
586
+
587
+ ---
588
+
589
+ ## Advanced Features
590
+
591
+ ### Chain - Compose Functions
592
+
593
+ Chain multiple functions for `apply_func`:
594
+
595
+ ```python
596
+ from fastschema import FieldConfig, RouteField, Chain
597
+
598
+ def strip(v):
599
+ return v.strip() if isinstance(v, str) else v
600
+
601
+ def upper(v):
602
+ return v.upper() if isinstance(v, str) else v
603
+
604
+ def remove_spaces(v):
605
+ return v.replace(" ", "_") if isinstance(v, str) else v
606
+
607
+ class Add(FieldConfig):
608
+ required = True
609
+
610
+ class ProductRoute(RouteBase):
611
+ title: str = RouteField(
612
+ add=Add(apply_func=Chain(strip, upper, remove_spaces)),
613
+ )
614
+
615
+ # " hello world " → "hello_world"
616
+ ```
617
+
618
+ ---
619
+
620
+ ### SelfDerivedModel - Bulk Operations
621
+
622
+ Derive a field's schema from the route's own fields:
623
+
624
+ ```python
625
+ from fastschema import FieldConfig, RouteField, RouteBase, SelfDerivedModel
626
+
627
+ class Add(FieldConfig):
628
+ required = True
629
+
630
+ class Output(FieldConfig):
631
+ pass
632
+
633
+ class BulkAdd(FieldConfig):
634
+ required = True
635
+
636
+ class BulkEdit(FieldConfig):
637
+ default = None
638
+
639
+ class UserRoute(RouteBase):
640
+ name: str = RouteField(add=Add(), output=Output())
641
+ email: str = RouteField(add=Add(), output=Output())
642
+
643
+ items: list = RouteField(
644
+ bulk_add=BulkAdd(
645
+ default=SelfDerivedModel(schema="add", exclude_fields=["email"])
646
+ ),
647
+ )
648
+
649
+ # UserRoute.schema("bulk_add") => items: list[name: str] (email excluded)
650
+ ```
651
+
652
+ ---
653
+
654
+ ### ClassVar Fields
655
+
656
+ Use `ClassVar` for fields not in the database but available for schema generation:
657
+
658
+ ```python
659
+ from typing import ClassVar
660
+
661
+ class UserRoute(RouteBase, Document):
662
+ name: str = RouteField(add=Add(), edit=Edit())
663
+
664
+ # ClassVar — not in DB, available for schema generation
665
+ token: ClassVar[str | None] = RouteField(get=Get(), add=Add(exclude=True))
666
+
667
+ # token is NOT in DB columns
668
+ # token IS in _fields for schema generation
669
+ # token appears in "get" schema, excluded from "add" schema
670
+ ```
671
+
672
+ ---
673
+
674
+ ### from_schema()
675
+
676
+ Create Document instances without re-validating nested models:
677
+
678
+ ```python
679
+ class UserRoute(RouteBase, Document):
680
+ name: str = RouteField(add=Add())
681
+
682
+ @classmethod
683
+ @route(path="/users", method="POST", status_code=201)
684
+ async def create_user(cls, request: Request, data: UserRoute.schema("add")):
685
+ # WRONG — re-validates nested models
686
+ # user = cls(**data.dict())
687
+
688
+ # CORRECT — preserves already-validated instances
689
+ user = cls.from_schema(data)
690
+ await user.insert()
691
+ return {"id": str(user.id)}
692
+ ```
693
+
694
+ ---
695
+
696
+ ### Validators
697
+
698
+ Validators defined on the route class are propagated to generated models:
699
+
700
+ ```python
701
+ from pydantic import BaseModel, field_validator
702
+
703
+ class Data(BaseModel):
704
+ username: str
705
+ password: str
706
+
707
+ @field_validator("username", mode="before")
708
+ @classmethod
709
+ def validate_username(cls, v):
710
+ return v.strip()
711
+
712
+ class UserRoute(RouteBase, Document):
713
+ data: Data = RouteField(add=Add())
714
+
715
+ @field_validator("name", mode="before")
716
+ @classmethod
717
+ def validate_name(cls, v):
718
+ return v.strip()
719
+
720
+ # Both validators run when UserRoute.schema("add") is used
721
+ ```
722
+
723
+ ---
724
+
725
+ ### Constraint Params
726
+
727
+ All Pydantic constraint params work in `RouteField`:
728
+
729
+ ```python
730
+ class ProductRoute(RouteBase):
731
+ title: str = RouteField(add=Add(), min_length=1, max_length=200)
732
+ price: float = RouteField(add=Add(), gt=0, le=9999.99)
733
+ quantity: int = RouteField(add=Add(), ge=0, multiple_of=1)
734
+ sku: str = RouteField(add=Add(), pattern=r"^[A-Z]{3}-\d{4}$")
735
+ description: str = RouteField(add=Add(), max_length=5000)
736
+ ```
737
+
738
+ ---
739
+
740
+ ## API Reference
741
+
742
+ ### FieldConfig
743
+
744
+ ```python
745
+ class FieldConfig:
746
+ required: bool = False
747
+ default: Any = _UNSET
748
+ default_factory: Callable | None = None
749
+ alias: str | None = None
750
+ description: str | None = None
751
+ type_override: type | None = None
752
+ exclude: bool = False
753
+ frozen: bool = False
754
+ apply_func: Callable | None = None
755
+ before: bool = True
756
+ metadata: dict | None = None
757
+ ```
758
+
759
+ ### RouteField
760
+
761
+ ```python
762
+ class RouteField(PydanticFieldInfo):
763
+ def __init__(
764
+ self,
765
+ default: Any = _UNSET,
766
+ *,
767
+ default_factory: Callable | None = None,
768
+ alias: str | None = None,
769
+ validation_alias: str | None = None,
770
+ serialization_alias: str | None = None,
771
+ description: str | None = None,
772
+ title: str | None = None,
773
+ exclude: bool = False,
774
+ frozen: bool = False,
775
+ deprecated: str | None = None,
776
+ json_schema_extra: dict | None = None,
777
+ validate_default: bool = False,
778
+ repr: bool = True,
779
+ **kwargs: Any, # Pydantic constraint params + schema configs
780
+ ): ...
781
+ ```
782
+
783
+ ### RouteBase
784
+
785
+ ```python
786
+ class RouteBase(BaseModel, metaclass=RouteMetaclass):
787
+ @classmethod
788
+ def schema(
789
+ cls,
790
+ schema_type: str,
791
+ *,
792
+ name: str | None = None,
793
+ include_fields: list[str] | None = None,
794
+ exclude_fields: list[str] | None = None,
795
+ forbid_extra: bool = True,
796
+ as_literal: bool = False,
797
+ ) -> type[BaseModel]: ...
798
+
799
+ @classmethod
800
+ def Schema(cls, schema_type: str) -> type[BaseModel]: ...
801
+
802
+ @classmethod
803
+ def schema_fields(cls, schema_type: str) -> list[str]: ...
804
+
805
+ @classmethod
806
+ def all_fields(cls) -> dict[str, FieldInfo]: ...
807
+
808
+ @classmethod
809
+ def field_names(cls) -> list[str]: ...
810
+
811
+ @classmethod
812
+ def schema_types(cls) -> list[str]: ...
813
+
814
+ @classmethod
815
+ def from_schema(cls, data) -> Self: ...
816
+
817
+ @classmethod
818
+ def field_config_for(cls, field_name: str, schema_type: str) -> FieldConfig | None: ...
819
+ ```
820
+
821
+ ### SelfDerivedModel
822
+
823
+ ```python
824
+ class SelfDerivedModel:
825
+ def __init__(
826
+ self,
827
+ schema: str,
828
+ is_optional: bool = True,
829
+ format: str = "model",
830
+ include_fields: list[str] | None = None,
831
+ exclude_fields: list[str] | None = None,
832
+ ): ...
833
+ ```
834
+
835
+ ### route
836
+
837
+ ```python
838
+ def route(
839
+ path: str,
840
+ method: Literal["GET", "POST", "PUT", "DELETE", "PATCH"] = "GET",
841
+ name: str | None = None,
842
+ description: str | None = None,
843
+ status_code: int = 200,
844
+ tags: list[str] | None = None,
845
+ ) -> Callable: ...
846
+ ```
847
+
848
+ ### route_factory
849
+
850
+ ```python
851
+ def route_factory(*route_classes: type) -> APIRouter: ...
852
+ ```
853
+
854
+ ### Chain
855
+
856
+ ```python
857
+ class Chain:
858
+ def __init__(self, *funcs: Callable): ...
859
+ def __call__(self, v: Any) -> Any: ...
860
+ ```
861
+
862
+ ---
863
+
864
+ ## Skills
865
+
866
+ fastschema includes a `SKILL.md` file for AI code agents (Codex, MiMo, etc.). This file provides agents with specialized knowledge to work effectively with the fastschema package.
867
+
868
+ **What it covers:**
869
+ - Core concepts (FieldConfig, RouteField, RouteBase, @route, route_factory)
870
+ - ORM integration patterns (SQLModel, Beanie)
871
+ - Advanced features (SelfDerivedModel, Chain, ClassVar, validators)
872
+ - Common CRUD and bulk operation patterns
873
+
874
+ **Usage:** Agents automatically discover and load the skill when working with fastschema-related tasks. The skill file is located at the project root alongside `README.md`.
875
+
876
+ ---
877
+
878
+ ## License
879
+
880
+ Apache License 2.0 — see [LICENSE](LICENSE) for details.