ohmyapi 0.2.1__tar.gz → 0.2.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. ohmyapi-0.2.2/PKG-INFO +75 -0
  2. ohmyapi-0.2.2/README.md +40 -0
  3. {ohmyapi-0.2.1 → ohmyapi-0.2.2}/pyproject.toml +3 -1
  4. {ohmyapi-0.2.1 → ohmyapi-0.2.2}/src/ohmyapi/db/model/model.py +6 -3
  5. ohmyapi-0.2.1/PKG-INFO +0 -436
  6. ohmyapi-0.2.1/README.md +0 -401
  7. {ohmyapi-0.2.1 → ohmyapi-0.2.2}/src/ohmyapi/__init__.py +0 -0
  8. {ohmyapi-0.2.1 → ohmyapi-0.2.2}/src/ohmyapi/__main__.py +0 -0
  9. {ohmyapi-0.2.1 → ohmyapi-0.2.2}/src/ohmyapi/builtin/auth/__init__.py +0 -0
  10. {ohmyapi-0.2.1 → ohmyapi-0.2.2}/src/ohmyapi/builtin/auth/models.py +0 -0
  11. {ohmyapi-0.2.1 → ohmyapi-0.2.2}/src/ohmyapi/builtin/auth/permissions.py +0 -0
  12. {ohmyapi-0.2.1 → ohmyapi-0.2.2}/src/ohmyapi/builtin/auth/routes.py +0 -0
  13. {ohmyapi-0.2.1 → ohmyapi-0.2.2}/src/ohmyapi/builtin/demo/__init__.py +0 -0
  14. {ohmyapi-0.2.1 → ohmyapi-0.2.2}/src/ohmyapi/builtin/demo/models.py +0 -0
  15. {ohmyapi-0.2.1 → ohmyapi-0.2.2}/src/ohmyapi/builtin/demo/routes.py +0 -0
  16. {ohmyapi-0.2.1 → ohmyapi-0.2.2}/src/ohmyapi/cli.py +0 -0
  17. {ohmyapi-0.2.1 → ohmyapi-0.2.2}/src/ohmyapi/core/__init__.py +0 -0
  18. {ohmyapi-0.2.1 → ohmyapi-0.2.2}/src/ohmyapi/core/runtime.py +0 -0
  19. {ohmyapi-0.2.1 → ohmyapi-0.2.2}/src/ohmyapi/core/scaffolding.py +0 -0
  20. {ohmyapi-0.2.1 → ohmyapi-0.2.2}/src/ohmyapi/core/templates/app/__init__.py.j2 +0 -0
  21. {ohmyapi-0.2.1 → ohmyapi-0.2.2}/src/ohmyapi/core/templates/app/models.py.j2 +0 -0
  22. {ohmyapi-0.2.1 → ohmyapi-0.2.2}/src/ohmyapi/core/templates/app/routes.py.j2 +0 -0
  23. {ohmyapi-0.2.1 → ohmyapi-0.2.2}/src/ohmyapi/core/templates/project/README.md.j2 +0 -0
  24. {ohmyapi-0.2.1 → ohmyapi-0.2.2}/src/ohmyapi/core/templates/project/pyproject.toml.j2 +0 -0
  25. {ohmyapi-0.2.1 → ohmyapi-0.2.2}/src/ohmyapi/core/templates/project/settings.py.j2 +0 -0
  26. {ohmyapi-0.2.1 → ohmyapi-0.2.2}/src/ohmyapi/db/__init__.py +0 -0
  27. {ohmyapi-0.2.1 → ohmyapi-0.2.2}/src/ohmyapi/db/exceptions.py +0 -0
  28. {ohmyapi-0.2.1 → ohmyapi-0.2.2}/src/ohmyapi/db/model/__init__.py +0 -0
  29. {ohmyapi-0.2.1 → ohmyapi-0.2.2}/src/ohmyapi/router.py +0 -0
ohmyapi-0.2.2/PKG-INFO ADDED
@@ -0,0 +1,75 @@
1
+ Metadata-Version: 2.4
2
+ Name: ohmyapi
3
+ Version: 0.2.2
4
+ Summary: Django-flavored scaffolding and management layer around FastAPI, Pydantic, TortoiseORM and Aerich migrations
5
+ License-Expression: MIT
6
+ Keywords: fastapi,tortoise,orm,pydantic,async,web-framework
7
+ Author: Brian Wiborg
8
+ Author-email: me@brianwib.org
9
+ Requires-Python: >=3.11
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.11
12
+ Classifier: Programming Language :: Python :: 3.12
13
+ Classifier: Programming Language :: Python :: 3.13
14
+ Classifier: Programming Language :: Python :: 3.14
15
+ Provides-Extra: auth
16
+ Requires-Dist: aerich (>=0.9.1,<0.10.0)
17
+ Requires-Dist: argon2-cffi (>=25.1.0,<26.0.0)
18
+ Requires-Dist: argon2-cffi ; extra == "auth"
19
+ Requires-Dist: crypto (>=1.4.1,<2.0.0)
20
+ Requires-Dist: crypto ; extra == "auth"
21
+ Requires-Dist: fastapi (>=0.117.1,<0.118.0)
22
+ Requires-Dist: ipython (>=9.5.0,<10.0.0)
23
+ Requires-Dist: jinja2 (>=3.1.6,<4.0.0)
24
+ Requires-Dist: passlib (>=1.7.4,<2.0.0)
25
+ Requires-Dist: passlib ; extra == "auth"
26
+ Requires-Dist: pyjwt (>=2.10.1,<3.0.0)
27
+ Requires-Dist: pyjwt ; extra == "auth"
28
+ Requires-Dist: python-multipart (>=0.0.20,<0.0.21)
29
+ Requires-Dist: python-multipart ; extra == "auth"
30
+ Requires-Dist: tortoise-orm (>=0.25.1,<0.26.0)
31
+ Requires-Dist: typer (>=0.19.1,<0.20.0)
32
+ Requires-Dist: uvicorn (>=0.36.0,<0.37.0)
33
+ Description-Content-Type: text/markdown
34
+
35
+ # OhMyAPI
36
+
37
+ OhMyAPI is a web-application scaffolding framework and management layer built around `FastAPI`, `TortoiseORM` and `Aerich` migrations.
38
+
39
+ > *Think: Django RestFramework, but less clunky and 100% async.*
40
+
41
+ It is ***blazingly fast***, extremely ***fun to use*** and comes with ***batteries included***!
42
+
43
+ **Features**
44
+
45
+ - Django-like project structure and application directories
46
+ - Django-like per-app migrations (makemigrations & migrate) via Aerich
47
+ - Django-like CLI tooling (startproject, startapp, shell, serve, etc)
48
+ - Customizable pydantic model serializer built-in
49
+ - Various optional built-in apps you can hook into your project
50
+ - Highly configurable and customizable
51
+ - 100% async
52
+
53
+ **Goals**
54
+
55
+ - combine FastAPI, TortoiseORM, Aerich migrations and Pydantic into a high-productivity web-application framework
56
+ - tie everything neatly together into a concise and straight-forward API
57
+ - AVOID adding any abstractions on top, unless they make things extremely convenient
58
+
59
+ ## Installation
60
+
61
+ ```
62
+ pipx install ohmyapi
63
+ ```
64
+
65
+
66
+ ## Docs
67
+
68
+ See: `docs/`
69
+
70
+ - [Projects](docs/projects.md)
71
+ - [Apps](docs/apps.md)
72
+ - [Models](docs/models.md)
73
+ - [Migrations](docs/migrations.md)
74
+ - [Routes](docs/routes.md)
75
+
@@ -0,0 +1,40 @@
1
+ # OhMyAPI
2
+
3
+ OhMyAPI is a web-application scaffolding framework and management layer built around `FastAPI`, `TortoiseORM` and `Aerich` migrations.
4
+
5
+ > *Think: Django RestFramework, but less clunky and 100% async.*
6
+
7
+ It is ***blazingly fast***, extremely ***fun to use*** and comes with ***batteries included***!
8
+
9
+ **Features**
10
+
11
+ - Django-like project structure and application directories
12
+ - Django-like per-app migrations (makemigrations & migrate) via Aerich
13
+ - Django-like CLI tooling (startproject, startapp, shell, serve, etc)
14
+ - Customizable pydantic model serializer built-in
15
+ - Various optional built-in apps you can hook into your project
16
+ - Highly configurable and customizable
17
+ - 100% async
18
+
19
+ **Goals**
20
+
21
+ - combine FastAPI, TortoiseORM, Aerich migrations and Pydantic into a high-productivity web-application framework
22
+ - tie everything neatly together into a concise and straight-forward API
23
+ - AVOID adding any abstractions on top, unless they make things extremely convenient
24
+
25
+ ## Installation
26
+
27
+ ```
28
+ pipx install ohmyapi
29
+ ```
30
+
31
+
32
+ ## Docs
33
+
34
+ See: `docs/`
35
+
36
+ - [Projects](docs/projects.md)
37
+ - [Apps](docs/apps.md)
38
+ - [Models](docs/models.md)
39
+ - [Migrations](docs/migrations.md)
40
+ - [Routes](docs/routes.md)
@@ -1,12 +1,13 @@
1
1
  [project]
2
2
  name = "ohmyapi"
3
- version = "0.2.1"
3
+ version = "0.2.2"
4
4
  description = "Django-flavored scaffolding and management layer around FastAPI, Pydantic, TortoiseORM and Aerich migrations"
5
5
  license = "MIT"
6
6
  keywords = ["fastapi", "tortoise", "orm", "pydantic", "async", "web-framework"]
7
7
  authors = [
8
8
  {name = "Brian Wiborg", email = "me@brianwib.org"}
9
9
  ]
10
+ repository = "https://code.c-base.org/baccenfutter/ohmyapi"
10
11
  readme = "README.md"
11
12
  requires-python = ">=3.11"
12
13
 
@@ -29,6 +30,7 @@ dependencies = [
29
30
  ipython = ">=9.5.0,<10.0.0"
30
31
  black = "^25.9.0"
31
32
  isort = "^6.0.1"
33
+ mkdocs = "^1.6.1"
32
34
 
33
35
  [project.optional-dependencies]
34
36
  auth = ["passlib", "pyjwt", "crypto", "argon2-cffi", "python-multipart"]
@@ -39,9 +39,7 @@ class ModelMeta(type(TortoiseModel)):
39
39
 
40
40
  class BoundSchema:
41
41
  def __call__(self, readonly: bool = False):
42
- if readonly:
43
- return self.readonly
44
- return self.model
42
+ return self.get(readonly)
45
43
 
46
44
  @property
47
45
  def model(self):
@@ -68,6 +66,11 @@ class ModelMeta(type(TortoiseModel)):
68
66
  exclude_readonly=True,
69
67
  )
70
68
 
69
+ def get(self, readonly: bool = False):
70
+ if readonly:
71
+ return self.readonly
72
+ return self.model
73
+
71
74
  new_cls.Schema = BoundSchema()
72
75
  return new_cls
73
76
 
ohmyapi-0.2.1/PKG-INFO DELETED
@@ -1,436 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: ohmyapi
3
- Version: 0.2.1
4
- Summary: Django-flavored scaffolding and management layer around FastAPI, Pydantic, TortoiseORM and Aerich migrations
5
- License-Expression: MIT
6
- Keywords: fastapi,tortoise,orm,pydantic,async,web-framework
7
- Author: Brian Wiborg
8
- Author-email: me@brianwib.org
9
- Requires-Python: >=3.11
10
- Classifier: Programming Language :: Python :: 3
11
- Classifier: Programming Language :: Python :: 3.11
12
- Classifier: Programming Language :: Python :: 3.12
13
- Classifier: Programming Language :: Python :: 3.13
14
- Classifier: Programming Language :: Python :: 3.14
15
- Provides-Extra: auth
16
- Requires-Dist: aerich (>=0.9.1,<0.10.0)
17
- Requires-Dist: argon2-cffi (>=25.1.0,<26.0.0)
18
- Requires-Dist: argon2-cffi ; extra == "auth"
19
- Requires-Dist: crypto (>=1.4.1,<2.0.0)
20
- Requires-Dist: crypto ; extra == "auth"
21
- Requires-Dist: fastapi (>=0.117.1,<0.118.0)
22
- Requires-Dist: ipython (>=9.5.0,<10.0.0)
23
- Requires-Dist: jinja2 (>=3.1.6,<4.0.0)
24
- Requires-Dist: passlib (>=1.7.4,<2.0.0)
25
- Requires-Dist: passlib ; extra == "auth"
26
- Requires-Dist: pyjwt (>=2.10.1,<3.0.0)
27
- Requires-Dist: pyjwt ; extra == "auth"
28
- Requires-Dist: python-multipart (>=0.0.20,<0.0.21)
29
- Requires-Dist: python-multipart ; extra == "auth"
30
- Requires-Dist: tortoise-orm (>=0.25.1,<0.26.0)
31
- Requires-Dist: typer (>=0.19.1,<0.20.0)
32
- Requires-Dist: uvicorn (>=0.36.0,<0.37.0)
33
- Description-Content-Type: text/markdown
34
-
35
- # OhMyAPI
36
-
37
- > Think: Django RestFramework, but less clunky and 100% async.
38
-
39
- OhMyAPI is a Django-flavored web-application scaffolding framework and management layer,
40
- built around FastAPI and TortoiseORM and is thus 100% async.
41
-
42
- It is ***blazingly fast***, extremely ***fun to use*** and comes with ***batteries included***!
43
-
44
- **Features**
45
-
46
- - Django-like project structure and application directories
47
- - Django-like per-app migrations (`makemigrations` & `migrate`) via Aerich
48
- - Django-like CLI tooling (`startproject`, `startapp`, `shell`, `serve`, etc)
49
- - Customizable pydantic model serializer built-in
50
- - Various optional built-in apps you can hook into your project
51
- - Highly configurable and customizable
52
- - 100% async
53
-
54
- **Goals**
55
-
56
- - combine `FastAPI`, `TortoiseORM`, `Aerich` migrations and `Pydantic` into a high-productivity web-application framework
57
- - tie everything neatly together into a concise and straight-forward API
58
- - ***AVOID*** adding any abstractions on top, unless they make things extremely convenient
59
-
60
- ---
61
-
62
- ## Getting started
63
-
64
- **Creating a Project**
65
-
66
- ```
67
- pipx install ohmyapi
68
- ohmyapi startproject myproject
69
- cd myproject
70
- ```
71
-
72
- This will create the following directory structure:
73
-
74
- ```
75
- myproject/
76
- - pyproject.toml
77
- - README.md
78
- - settings.py
79
- ```
80
-
81
- Run your project with:
82
-
83
- ```
84
- ohmyapi serve
85
- ```
86
-
87
- In your browser go to:
88
- - http://localhost:8000/docs
89
-
90
- **Creating an App**
91
-
92
- Create a new app by:
93
-
94
- ```
95
- ohmyapi startapp tournament
96
- ```
97
-
98
- This will create the following directory structure:
99
-
100
- ```
101
- myproject/
102
- - tournament/
103
- - __init__.py
104
- - models.py
105
- - routes.py
106
- - pyproject.toml
107
- - README.md
108
- - settings.py
109
- ```
110
-
111
- Add 'tournament' to your `INSTALLED_APPS` in `settings.py`.
112
-
113
- ### Models
114
-
115
- Write your first model in `turnament/models.py`:
116
-
117
- ```python
118
- from ohmyapi.db import Model, field
119
-
120
- from datetime import datetime
121
- from decimal import Decimal
122
- from uuid import UUID
123
-
124
-
125
- class Tournament(Model):
126
- id: UUID = field.data.UUIDField(primary_key=True)
127
- name: str = field.TextField()
128
- created: datetime = field.DatetimeField(auto_now_add=True)
129
-
130
- def __str__(self):
131
- return self.name
132
-
133
-
134
- class Event(Model):
135
- id: UUID = field.data.UUIDField(primary_key=True)
136
- name: str = field.TextField()
137
- tournament: UUID = field.ForeignKeyField('tournament.Tournament', related_name='events')
138
- participants: field.ManyToManyRelation[Team] = field.ManyToManyField('tournament.Team', related_name='events', through='event_team')
139
- modified: datetime = field.DatetimeField(auto_now=True)
140
- prize: Decimal = field.DecimalField(max_digits=10, decimal_places=2, null=True)
141
-
142
- def __str__(self):
143
- return self.name
144
-
145
-
146
- class Team(Model):
147
- id: UUID = field.data.UUIDField(primary_key=True)
148
- name: str = field.TextField()
149
-
150
- def __str__(self):
151
- return self.name
152
- ```
153
-
154
- ### API Routes
155
-
156
- Next, create your endpoints in `tournament/routes.py`:
157
-
158
- ```python
159
- from ohmyapi.router import APIRouter, HTTPException, HTTPStatus
160
- from ohmyapi.db.exceptions import DoesNotExist
161
-
162
- from typing import List
163
-
164
- from .models import Tournament
165
-
166
- # OhMyAPI will automatically pick up all instances of `fastapi.APIRouter` and
167
- # add their routes to the main project router.
168
- #
169
- # Note:
170
- # Use prefixes wisely to avoid cross-app namespace-collisions!
171
- # Tags improve the UX of the OpenAPI docs at /docs.
172
- #
173
- tournament_router = APIRouter(prefix="/tournament", tags=['Tournament'])
174
-
175
-
176
- @tournament_router.get("/", response_model=List[Tournament.Schema()])
177
- async def list():
178
- queryset = Tournament.all()
179
- return await Tournament.Schema.model.from_queryset(queryset)
180
-
181
-
182
- @tournament_router.post("/", status_code=HTTPStatus.CREATED)
183
- async def post(tournament: Tournament.Schema(readonly=True)):
184
- queryset = Tournament.create(**payload.model_dump())
185
- return await Tournament.Schema().from_queryset(queryset)
186
-
187
-
188
- @tournament_router.get("/:id", response_model=Tournament.Schema())
189
- async def get(id: str):
190
- try:
191
- queryset = Tournament.get(id=id)
192
- return await Tournament.Schema().from_queryset_single(tournament)
193
- except DoesNotExist:
194
- raise HTTPException(status_code=404, detail="not found")
195
-
196
-
197
- @tournament_router.delete("/:id")
198
- async def delete(id: str):
199
- try:
200
- tournament = await Tournament.get(id=id)
201
- return await Tournament.Schema.model.from_queryset(tournament.delete())
202
- except DoesNotExist:
203
- raise HTTPException(status_code=404, detail="not found")
204
-
205
-
206
- ...
207
- ```
208
-
209
- ## Migrations
210
-
211
- Before we can run the app, we need to create and initialize the database.
212
-
213
- Similar to Django, first run:
214
-
215
- ```
216
- ohmyapi makemigrations [ <app> ] # no app means all INSTALLED_APPS
217
- ```
218
-
219
- This will create a `migrations/` folder in you project root.
220
-
221
- ```
222
- myproject/
223
- - tournament/
224
- - __init__.py
225
- - models.py
226
- - routes.py
227
- - migrations/
228
- - tournament/
229
- - pyproject.toml
230
- - README.md
231
- - settings.py
232
- ```
233
-
234
- Apply your migrations via:
235
-
236
- ```
237
- ohmyapi migrate [ <app> ] # no app means all INSTALLED_APPS
238
- ```
239
-
240
- Run your project:
241
-
242
- ```
243
- ohmyapi serve
244
- ```
245
-
246
- ## Authentication
247
-
248
- A builtin auth app is available.
249
-
250
- Simply add `ohmyapi_auth` to your INSTALLED_APPS and define a JWT_SECRET in your `settings.py`.
251
- Remember to `makemigrations` and `migrate` for the necessary tables to be created in the database.
252
-
253
- `settings.py`:
254
-
255
- ```
256
- INSTALLED_APPS = [
257
- 'ohmyapi_auth',
258
- ...
259
- ]
260
-
261
- JWT_SECRET = "t0ps3cr3t"
262
- ```
263
-
264
- After restarting your project you will have access to the `ohmyapi_auth` app.
265
- It comes with a `User` and `Group` model, as well as endpoints for JWT auth.
266
-
267
- You can use the models as `ForeignKeyField` in your application models:
268
-
269
- ```python
270
- from ohmyapi.db import Model, field
271
- from ohmyapi_auth.models import User
272
-
273
-
274
- class Team(Model):
275
- [...]
276
- members: field.ManyToManyRelation[User] = field.ManyToManyField('ohmyapi_auth.User', related_name='tournament_teams', through='tournament_teams')
277
- [...]
278
- ```
279
-
280
- Remember to run `makemigrations` and `migrate` in order for your model changes to take effect in the database.
281
-
282
- Create a super-user:
283
-
284
- ```
285
- ohmyapi createsuperuser
286
- ```
287
-
288
- ## Permissions
289
-
290
- ### API-Level Permissions
291
-
292
- Use FastAPI's `Depends` pattern to implement API-level access-control.
293
-
294
-
295
- In your `routes.py`:
296
-
297
- ```python
298
- from ohmyapi.router import APIRouter, Depends
299
- from ohmyapi_auth import (
300
- models as auth,
301
- permissions,
302
- )
303
-
304
- from .models import Tournament
305
-
306
- router = APIRouter(prefix="/tournament", tags=["Tournament"])
307
-
308
-
309
- @router.get("/")
310
- async def list(user: auth.User = Depends(permissions.require_authenticated)):
311
- queryset = Tournament.all()
312
- return await Tournament.Schema().from_queryset(queryset)
313
-
314
-
315
- ...
316
- ```
317
-
318
- ### Model-Level Permissions
319
-
320
- Use Tortoise's `Manager` to implement model-level permissions.
321
-
322
- ```python
323
- from ohmyapi.db import Manager
324
- from ohmyapi_auth.models import User
325
-
326
-
327
- class TeamManager(Manager):
328
- async def for_user(self, user: User):
329
- return await self.filter(members=user).all()
330
-
331
-
332
- class Team(Model):
333
- [...]
334
-
335
- class Meta:
336
- manager = TeamManager()
337
- ```
338
-
339
- Use the custom manager in your FastAPI route handler:
340
-
341
- ```python
342
- from ohmyapi.router import APIRouter
343
- from ohmyapi_auth import (
344
- models as auth,
345
- permissions,
346
- )
347
-
348
- router = APIRouter(prefix="/tournament", tags=["Tournament"])
349
-
350
-
351
- @router.get("/teams")
352
- async def teams(user: auth.User = Depends(permissions.require_authenticated)):
353
- queryset = Team.for_user(user)
354
- return await Tournament.Schema().from_queryset(queryset)
355
- ```
356
-
357
- ## Shell
358
-
359
- Similar to Django, you can attach to an interactive shell with your project already loaded inside.
360
-
361
- ```
362
- ohmyapi shell
363
-
364
- Python 3.13.7 (main, Aug 15 2025, 12:34:02) [GCC 15.2.1 20250813]
365
- Type 'copyright', 'credits' or 'license' for more information
366
- IPython 9.5.0 -- An enhanced Interactive Python. Type '?' for help.
367
-
368
- OhMyAPI Shell | Project: {{ project_name }} [{{ project_path }}]
369
- Find your loaded project singleton via identifier: `p`
370
- ```
371
-
372
- ```python
373
- In [1]: p
374
- Out[1]: <ohmyapi.core.runtime.Project at 0x7f00c43dbcb0>
375
-
376
- In [2]: p.apps
377
- Out[2]:
378
- {'ohmyapi_auth': {
379
- "models": [
380
- "Group",
381
- "User"
382
- ],
383
- "routes": [
384
- {
385
- "path": "/auth/login",
386
- "name": "login",
387
- "methods": [
388
- "POST"
389
- ],
390
- "endpoint": "login",
391
- "response_model": null,
392
- "tags": [
393
- "auth"
394
- ]
395
- },
396
- {
397
- "path": "/auth/refresh",
398
- "name": "refresh_token",
399
- "methods": [
400
- "POST"
401
- ],
402
- "endpoint": "refresh_token",
403
- "response_model": null,
404
- "tags": [
405
- "auth"
406
- ]
407
- },
408
- {
409
- "path": "/auth/introspect",
410
- "name": "introspect",
411
- "methods": [
412
- "GET"
413
- ],
414
- "endpoint": "introspect",
415
- "response_model": null,
416
- "tags": [
417
- "auth"
418
- ]
419
- },
420
- {
421
- "path": "/auth/me",
422
- "name": "me",
423
- "methods": [
424
- "GET"
425
- ],
426
- "endpoint": "me",
427
- "response_model": null,
428
- "tags": [
429
- "auth"
430
- ]
431
- }
432
- ]
433
- }}
434
- ```
435
-
436
-
ohmyapi-0.2.1/README.md DELETED
@@ -1,401 +0,0 @@
1
- # OhMyAPI
2
-
3
- > Think: Django RestFramework, but less clunky and 100% async.
4
-
5
- OhMyAPI is a Django-flavored web-application scaffolding framework and management layer,
6
- built around FastAPI and TortoiseORM and is thus 100% async.
7
-
8
- It is ***blazingly fast***, extremely ***fun to use*** and comes with ***batteries included***!
9
-
10
- **Features**
11
-
12
- - Django-like project structure and application directories
13
- - Django-like per-app migrations (`makemigrations` & `migrate`) via Aerich
14
- - Django-like CLI tooling (`startproject`, `startapp`, `shell`, `serve`, etc)
15
- - Customizable pydantic model serializer built-in
16
- - Various optional built-in apps you can hook into your project
17
- - Highly configurable and customizable
18
- - 100% async
19
-
20
- **Goals**
21
-
22
- - combine `FastAPI`, `TortoiseORM`, `Aerich` migrations and `Pydantic` into a high-productivity web-application framework
23
- - tie everything neatly together into a concise and straight-forward API
24
- - ***AVOID*** adding any abstractions on top, unless they make things extremely convenient
25
-
26
- ---
27
-
28
- ## Getting started
29
-
30
- **Creating a Project**
31
-
32
- ```
33
- pipx install ohmyapi
34
- ohmyapi startproject myproject
35
- cd myproject
36
- ```
37
-
38
- This will create the following directory structure:
39
-
40
- ```
41
- myproject/
42
- - pyproject.toml
43
- - README.md
44
- - settings.py
45
- ```
46
-
47
- Run your project with:
48
-
49
- ```
50
- ohmyapi serve
51
- ```
52
-
53
- In your browser go to:
54
- - http://localhost:8000/docs
55
-
56
- **Creating an App**
57
-
58
- Create a new app by:
59
-
60
- ```
61
- ohmyapi startapp tournament
62
- ```
63
-
64
- This will create the following directory structure:
65
-
66
- ```
67
- myproject/
68
- - tournament/
69
- - __init__.py
70
- - models.py
71
- - routes.py
72
- - pyproject.toml
73
- - README.md
74
- - settings.py
75
- ```
76
-
77
- Add 'tournament' to your `INSTALLED_APPS` in `settings.py`.
78
-
79
- ### Models
80
-
81
- Write your first model in `turnament/models.py`:
82
-
83
- ```python
84
- from ohmyapi.db import Model, field
85
-
86
- from datetime import datetime
87
- from decimal import Decimal
88
- from uuid import UUID
89
-
90
-
91
- class Tournament(Model):
92
- id: UUID = field.data.UUIDField(primary_key=True)
93
- name: str = field.TextField()
94
- created: datetime = field.DatetimeField(auto_now_add=True)
95
-
96
- def __str__(self):
97
- return self.name
98
-
99
-
100
- class Event(Model):
101
- id: UUID = field.data.UUIDField(primary_key=True)
102
- name: str = field.TextField()
103
- tournament: UUID = field.ForeignKeyField('tournament.Tournament', related_name='events')
104
- participants: field.ManyToManyRelation[Team] = field.ManyToManyField('tournament.Team', related_name='events', through='event_team')
105
- modified: datetime = field.DatetimeField(auto_now=True)
106
- prize: Decimal = field.DecimalField(max_digits=10, decimal_places=2, null=True)
107
-
108
- def __str__(self):
109
- return self.name
110
-
111
-
112
- class Team(Model):
113
- id: UUID = field.data.UUIDField(primary_key=True)
114
- name: str = field.TextField()
115
-
116
- def __str__(self):
117
- return self.name
118
- ```
119
-
120
- ### API Routes
121
-
122
- Next, create your endpoints in `tournament/routes.py`:
123
-
124
- ```python
125
- from ohmyapi.router import APIRouter, HTTPException, HTTPStatus
126
- from ohmyapi.db.exceptions import DoesNotExist
127
-
128
- from typing import List
129
-
130
- from .models import Tournament
131
-
132
- # OhMyAPI will automatically pick up all instances of `fastapi.APIRouter` and
133
- # add their routes to the main project router.
134
- #
135
- # Note:
136
- # Use prefixes wisely to avoid cross-app namespace-collisions!
137
- # Tags improve the UX of the OpenAPI docs at /docs.
138
- #
139
- tournament_router = APIRouter(prefix="/tournament", tags=['Tournament'])
140
-
141
-
142
- @tournament_router.get("/", response_model=List[Tournament.Schema()])
143
- async def list():
144
- queryset = Tournament.all()
145
- return await Tournament.Schema.model.from_queryset(queryset)
146
-
147
-
148
- @tournament_router.post("/", status_code=HTTPStatus.CREATED)
149
- async def post(tournament: Tournament.Schema(readonly=True)):
150
- queryset = Tournament.create(**payload.model_dump())
151
- return await Tournament.Schema().from_queryset(queryset)
152
-
153
-
154
- @tournament_router.get("/:id", response_model=Tournament.Schema())
155
- async def get(id: str):
156
- try:
157
- queryset = Tournament.get(id=id)
158
- return await Tournament.Schema().from_queryset_single(tournament)
159
- except DoesNotExist:
160
- raise HTTPException(status_code=404, detail="not found")
161
-
162
-
163
- @tournament_router.delete("/:id")
164
- async def delete(id: str):
165
- try:
166
- tournament = await Tournament.get(id=id)
167
- return await Tournament.Schema.model.from_queryset(tournament.delete())
168
- except DoesNotExist:
169
- raise HTTPException(status_code=404, detail="not found")
170
-
171
-
172
- ...
173
- ```
174
-
175
- ## Migrations
176
-
177
- Before we can run the app, we need to create and initialize the database.
178
-
179
- Similar to Django, first run:
180
-
181
- ```
182
- ohmyapi makemigrations [ <app> ] # no app means all INSTALLED_APPS
183
- ```
184
-
185
- This will create a `migrations/` folder in you project root.
186
-
187
- ```
188
- myproject/
189
- - tournament/
190
- - __init__.py
191
- - models.py
192
- - routes.py
193
- - migrations/
194
- - tournament/
195
- - pyproject.toml
196
- - README.md
197
- - settings.py
198
- ```
199
-
200
- Apply your migrations via:
201
-
202
- ```
203
- ohmyapi migrate [ <app> ] # no app means all INSTALLED_APPS
204
- ```
205
-
206
- Run your project:
207
-
208
- ```
209
- ohmyapi serve
210
- ```
211
-
212
- ## Authentication
213
-
214
- A builtin auth app is available.
215
-
216
- Simply add `ohmyapi_auth` to your INSTALLED_APPS and define a JWT_SECRET in your `settings.py`.
217
- Remember to `makemigrations` and `migrate` for the necessary tables to be created in the database.
218
-
219
- `settings.py`:
220
-
221
- ```
222
- INSTALLED_APPS = [
223
- 'ohmyapi_auth',
224
- ...
225
- ]
226
-
227
- JWT_SECRET = "t0ps3cr3t"
228
- ```
229
-
230
- After restarting your project you will have access to the `ohmyapi_auth` app.
231
- It comes with a `User` and `Group` model, as well as endpoints for JWT auth.
232
-
233
- You can use the models as `ForeignKeyField` in your application models:
234
-
235
- ```python
236
- from ohmyapi.db import Model, field
237
- from ohmyapi_auth.models import User
238
-
239
-
240
- class Team(Model):
241
- [...]
242
- members: field.ManyToManyRelation[User] = field.ManyToManyField('ohmyapi_auth.User', related_name='tournament_teams', through='tournament_teams')
243
- [...]
244
- ```
245
-
246
- Remember to run `makemigrations` and `migrate` in order for your model changes to take effect in the database.
247
-
248
- Create a super-user:
249
-
250
- ```
251
- ohmyapi createsuperuser
252
- ```
253
-
254
- ## Permissions
255
-
256
- ### API-Level Permissions
257
-
258
- Use FastAPI's `Depends` pattern to implement API-level access-control.
259
-
260
-
261
- In your `routes.py`:
262
-
263
- ```python
264
- from ohmyapi.router import APIRouter, Depends
265
- from ohmyapi_auth import (
266
- models as auth,
267
- permissions,
268
- )
269
-
270
- from .models import Tournament
271
-
272
- router = APIRouter(prefix="/tournament", tags=["Tournament"])
273
-
274
-
275
- @router.get("/")
276
- async def list(user: auth.User = Depends(permissions.require_authenticated)):
277
- queryset = Tournament.all()
278
- return await Tournament.Schema().from_queryset(queryset)
279
-
280
-
281
- ...
282
- ```
283
-
284
- ### Model-Level Permissions
285
-
286
- Use Tortoise's `Manager` to implement model-level permissions.
287
-
288
- ```python
289
- from ohmyapi.db import Manager
290
- from ohmyapi_auth.models import User
291
-
292
-
293
- class TeamManager(Manager):
294
- async def for_user(self, user: User):
295
- return await self.filter(members=user).all()
296
-
297
-
298
- class Team(Model):
299
- [...]
300
-
301
- class Meta:
302
- manager = TeamManager()
303
- ```
304
-
305
- Use the custom manager in your FastAPI route handler:
306
-
307
- ```python
308
- from ohmyapi.router import APIRouter
309
- from ohmyapi_auth import (
310
- models as auth,
311
- permissions,
312
- )
313
-
314
- router = APIRouter(prefix="/tournament", tags=["Tournament"])
315
-
316
-
317
- @router.get("/teams")
318
- async def teams(user: auth.User = Depends(permissions.require_authenticated)):
319
- queryset = Team.for_user(user)
320
- return await Tournament.Schema().from_queryset(queryset)
321
- ```
322
-
323
- ## Shell
324
-
325
- Similar to Django, you can attach to an interactive shell with your project already loaded inside.
326
-
327
- ```
328
- ohmyapi shell
329
-
330
- Python 3.13.7 (main, Aug 15 2025, 12:34:02) [GCC 15.2.1 20250813]
331
- Type 'copyright', 'credits' or 'license' for more information
332
- IPython 9.5.0 -- An enhanced Interactive Python. Type '?' for help.
333
-
334
- OhMyAPI Shell | Project: {{ project_name }} [{{ project_path }}]
335
- Find your loaded project singleton via identifier: `p`
336
- ```
337
-
338
- ```python
339
- In [1]: p
340
- Out[1]: <ohmyapi.core.runtime.Project at 0x7f00c43dbcb0>
341
-
342
- In [2]: p.apps
343
- Out[2]:
344
- {'ohmyapi_auth': {
345
- "models": [
346
- "Group",
347
- "User"
348
- ],
349
- "routes": [
350
- {
351
- "path": "/auth/login",
352
- "name": "login",
353
- "methods": [
354
- "POST"
355
- ],
356
- "endpoint": "login",
357
- "response_model": null,
358
- "tags": [
359
- "auth"
360
- ]
361
- },
362
- {
363
- "path": "/auth/refresh",
364
- "name": "refresh_token",
365
- "methods": [
366
- "POST"
367
- ],
368
- "endpoint": "refresh_token",
369
- "response_model": null,
370
- "tags": [
371
- "auth"
372
- ]
373
- },
374
- {
375
- "path": "/auth/introspect",
376
- "name": "introspect",
377
- "methods": [
378
- "GET"
379
- ],
380
- "endpoint": "introspect",
381
- "response_model": null,
382
- "tags": [
383
- "auth"
384
- ]
385
- },
386
- {
387
- "path": "/auth/me",
388
- "name": "me",
389
- "methods": [
390
- "GET"
391
- ],
392
- "endpoint": "me",
393
- "response_model": null,
394
- "tags": [
395
- "auth"
396
- ]
397
- }
398
- ]
399
- }}
400
- ```
401
-
File without changes
File without changes
File without changes
File without changes