ohmyapi 0.1.5__py3-none-any.whl → 0.1.7__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.
- ohmyapi/__main__.py +3 -0
- ohmyapi/cli.py +0 -3
- ohmyapi/core/templates/project/README.md.j2 +2 -0
- ohmyapi/core/templates/project/pyproject.toml.j2 +32 -8
- ohmyapi/db/__init__.py +2 -2
- ohmyapi/db/exceptions.py +2 -0
- ohmyapi/db/model/__init__.py +1 -1
- ohmyapi/db/model/model.py +1 -1
- ohmyapi/router.py +1 -1
- ohmyapi-0.1.7.dist-info/METADATA +310 -0
- {ohmyapi-0.1.5.dist-info → ohmyapi-0.1.7.dist-info}/RECORD +13 -11
- ohmyapi-0.1.7.dist-info/entry_points.txt +3 -0
- ohmyapi/db/migration_manager.py +0 -90
- ohmyapi-0.1.5.dist-info/METADATA +0 -204
- ohmyapi-0.1.5.dist-info/entry_points.txt +0 -3
- {ohmyapi-0.1.5.dist-info → ohmyapi-0.1.7.dist-info}/WHEEL +0 -0
ohmyapi/__main__.py
ADDED
ohmyapi/cli.py
CHANGED
@@ -1,13 +1,37 @@
|
|
1
|
-
[
|
1
|
+
[project]
|
2
2
|
name = "{{ project_name }}"
|
3
3
|
version = "0.1.0"
|
4
4
|
description = "OhMyAPI project"
|
5
|
-
authors = [
|
5
|
+
authors = [
|
6
|
+
{ name = "You", email = "you@you.tld" }
|
7
|
+
]
|
8
|
+
requires-python = ">=3.13"
|
9
|
+
readme = "README.md"
|
10
|
+
license = { text = "MIT" }
|
11
|
+
|
12
|
+
dependencies = [
|
13
|
+
"typer >=0.19.1,<0.20.0",
|
14
|
+
"jinja2 >=3.1.6,<4.0.0",
|
15
|
+
"fastapi >=0.117.1,<0.118.0",
|
16
|
+
"tortoise-orm >=0.25.1,<0.26.0",
|
17
|
+
"aerich >=0.9.1,<0.10.0",
|
18
|
+
"uvicorn >=0.36.0,<0.37.0",
|
19
|
+
"ipython >=9.5.0,<10.0.0",
|
20
|
+
"passlib >=1.7.4,<2.0.0",
|
21
|
+
"pyjwt >=2.10.1,<3.0.0",
|
22
|
+
"python-multipart >=0.0.20,<0.0.21",
|
23
|
+
"crypto >=1.4.1,<2.0.0",
|
24
|
+
"argon2-cffi >=25.1.0,<26.0.0",
|
25
|
+
]
|
26
|
+
|
27
|
+
[tool.poetry.group.dev.dependencies]
|
28
|
+
ipython = ">=9.5.0,<10.0.0"
|
6
29
|
|
7
|
-
[
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
aerich = "^0.7"
|
30
|
+
[project.optional-dependencies]
|
31
|
+
auth = ["passlib", "pyjwt", "crypto", "argon2-cffi", "python-multipart"]
|
32
|
+
|
33
|
+
[tool.poetry]
|
34
|
+
package-mode = false
|
13
35
|
|
36
|
+
[project.scripts]
|
37
|
+
{{ project_name }} = "ohmyapi.cli:app"
|
ohmyapi/db/__init__.py
CHANGED
@@ -1,3 +1,3 @@
|
|
1
|
-
from
|
2
|
-
from .
|
1
|
+
from .model import Model, field
|
2
|
+
from tortoise.manager import Manager
|
3
3
|
|
ohmyapi/db/exceptions.py
ADDED
ohmyapi/db/model/__init__.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
from .model import Model,
|
1
|
+
from .model import Model, field
|
ohmyapi/db/model/model.py
CHANGED
ohmyapi/router.py
CHANGED
@@ -1,2 +1,2 @@
|
|
1
|
-
from fastapi import APIRouter, Depends
|
1
|
+
from fastapi import APIRouter, Depends, HTTPException
|
2
2
|
|
@@ -0,0 +1,310 @@
|
|
1
|
+
Metadata-Version: 2.4
|
2
|
+
Name: ohmyapi
|
3
|
+
Version: 0.1.7
|
4
|
+
Summary: A Django-like but async web-framework based on FastAPI and TortoiseORM.
|
5
|
+
License-Expression: MIT
|
6
|
+
Keywords: fastapi,tortoise,orm,async,web-framework
|
7
|
+
Author: Brian Wiborg
|
8
|
+
Author-email: me@brianwib.org
|
9
|
+
Requires-Python: >=3.13
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
11
|
+
Classifier: Programming Language :: Python :: 3.13
|
12
|
+
Classifier: Programming Language :: Python :: 3.14
|
13
|
+
Provides-Extra: auth
|
14
|
+
Requires-Dist: aerich (>=0.9.1,<0.10.0)
|
15
|
+
Requires-Dist: argon2-cffi (>=25.1.0,<26.0.0)
|
16
|
+
Requires-Dist: argon2-cffi ; extra == "auth"
|
17
|
+
Requires-Dist: crypto (>=1.4.1,<2.0.0)
|
18
|
+
Requires-Dist: crypto ; extra == "auth"
|
19
|
+
Requires-Dist: fastapi (>=0.117.1,<0.118.0)
|
20
|
+
Requires-Dist: ipython (>=9.5.0,<10.0.0)
|
21
|
+
Requires-Dist: jinja2 (>=3.1.6,<4.0.0)
|
22
|
+
Requires-Dist: passlib (>=1.7.4,<2.0.0)
|
23
|
+
Requires-Dist: passlib ; extra == "auth"
|
24
|
+
Requires-Dist: pyjwt (>=2.10.1,<3.0.0)
|
25
|
+
Requires-Dist: pyjwt ; extra == "auth"
|
26
|
+
Requires-Dist: python-multipart (>=0.0.20,<0.0.21)
|
27
|
+
Requires-Dist: python-multipart ; extra == "auth"
|
28
|
+
Requires-Dist: tortoise-orm (>=0.25.1,<0.26.0)
|
29
|
+
Requires-Dist: typer (>=0.19.1,<0.20.0)
|
30
|
+
Requires-Dist: uvicorn (>=0.36.0,<0.37.0)
|
31
|
+
Description-Content-Type: text/markdown
|
32
|
+
|
33
|
+
# OhMyAPI
|
34
|
+
|
35
|
+
> Think: Micro-Django, but API-first, less clunky and 100% async.
|
36
|
+
|
37
|
+
OhMyAPI is a Django-flavored web-application scaffolding framework and management layer.
|
38
|
+
Built around FastAPI and TortoiseORM, it is 100% async.
|
39
|
+
|
40
|
+
It is ***blazingly fast***, ***fun*** to use and comes with ***batteries included***!
|
41
|
+
|
42
|
+
**Features**
|
43
|
+
|
44
|
+
- Django-like project-layout and -structure
|
45
|
+
- Django-like prject-level settings.py
|
46
|
+
- Django-like models via TortoiseORM
|
47
|
+
- Django-like `Model.Meta` class for model configuration
|
48
|
+
- Easily convert your query results to `pydantic` models via `Model.Schema`
|
49
|
+
- Django-like migrations (makemigrations & migrate) via Aerich
|
50
|
+
- Django-like CLI tooling (`startproject`, `startapp`, `shell`, `serve`, etc)
|
51
|
+
- Various optional builtin apps you can hook into your project
|
52
|
+
- Highly configurable and customizable
|
53
|
+
- 100% async
|
54
|
+
|
55
|
+
---
|
56
|
+
|
57
|
+
## Getting started
|
58
|
+
|
59
|
+
**Creating a Project**
|
60
|
+
|
61
|
+
```
|
62
|
+
pip install ohmyapi
|
63
|
+
ohmyapi startproject myproject
|
64
|
+
cd myproject
|
65
|
+
```
|
66
|
+
|
67
|
+
This will create the following directory structure:
|
68
|
+
|
69
|
+
```
|
70
|
+
myproject/
|
71
|
+
- pyproject.toml
|
72
|
+
- README.md
|
73
|
+
- settings.py
|
74
|
+
```
|
75
|
+
|
76
|
+
Run your project with:
|
77
|
+
|
78
|
+
```
|
79
|
+
ohmyapi serve
|
80
|
+
```
|
81
|
+
|
82
|
+
In your browser go to:
|
83
|
+
- http://localhost:8000/docs
|
84
|
+
|
85
|
+
**Creating an App**
|
86
|
+
|
87
|
+
Create a new app by:
|
88
|
+
|
89
|
+
```
|
90
|
+
ohmyapi startapp tournament
|
91
|
+
```
|
92
|
+
|
93
|
+
This will create the following directory structure:
|
94
|
+
|
95
|
+
```
|
96
|
+
myproject/
|
97
|
+
- tournament/
|
98
|
+
- __init__.py
|
99
|
+
- models.py
|
100
|
+
- routes.py
|
101
|
+
- pyproject.toml
|
102
|
+
- README.md
|
103
|
+
- settings.py
|
104
|
+
```
|
105
|
+
|
106
|
+
Add 'tournament' to your `INSTALLED_APPS` in `settings.py`.
|
107
|
+
|
108
|
+
### Models
|
109
|
+
|
110
|
+
Write your first model in `turnament/models.py`:
|
111
|
+
|
112
|
+
```python
|
113
|
+
from ohmyapi.db import Model, field
|
114
|
+
|
115
|
+
|
116
|
+
class Tournament(Model):
|
117
|
+
id = field.IntField(primary_key=True)
|
118
|
+
name = field.TextField()
|
119
|
+
created = field.DatetimeField(auto_now_add=True)
|
120
|
+
|
121
|
+
def __str__(self):
|
122
|
+
return self.name
|
123
|
+
|
124
|
+
|
125
|
+
class Event(Model):
|
126
|
+
id = field.IntField(primary_key=True)
|
127
|
+
name = field.TextField()
|
128
|
+
tournament = field.ForeignKeyField('tournament.Tournament', related_name='events')
|
129
|
+
participants = field.ManyToManyField('torunament.Team', related_name='events', through='event_team')
|
130
|
+
modified = field.DatetimeField(auto_now=True)
|
131
|
+
prize = field.DecimalField(max_digits=10, decimal_places=2, null=True)
|
132
|
+
|
133
|
+
def __str__(self):
|
134
|
+
return self.name
|
135
|
+
|
136
|
+
|
137
|
+
class Team(Model):
|
138
|
+
id = field.IntField(primary_key=True)
|
139
|
+
name = field.TextField()
|
140
|
+
|
141
|
+
def __str__(self):
|
142
|
+
return self.name
|
143
|
+
```
|
144
|
+
|
145
|
+
### API Routes
|
146
|
+
|
147
|
+
Next, create your endpoints in `tournament/routes.py`:
|
148
|
+
|
149
|
+
```python
|
150
|
+
from ohmyapi.router import APIRouter, HTTPException
|
151
|
+
from ohmyapi.db.exceptions import DoesNotExist
|
152
|
+
|
153
|
+
from .models import Tournament
|
154
|
+
|
155
|
+
router = APIRouter(prefix="/tournament")
|
156
|
+
|
157
|
+
|
158
|
+
@router.get("/")
|
159
|
+
async def list():
|
160
|
+
queryset = Tournament.all()
|
161
|
+
return await Tournament.Schema.many.from_queryset(queryset)
|
162
|
+
|
163
|
+
|
164
|
+
@router.get("/:id")
|
165
|
+
async def get(id: int):
|
166
|
+
try:
|
167
|
+
queryset = Tournament.get(pk=id)
|
168
|
+
return await Tournament.Schema.one(queryset)
|
169
|
+
except DoesNotExist:
|
170
|
+
raise HTTPException(status_code=404, detail="item not found")
|
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
|
+
## Shell
|
213
|
+
|
214
|
+
Similar to Django, you can attach to an interactive shell with your project already loaded inside.
|
215
|
+
|
216
|
+
```
|
217
|
+
ohmyapi shell
|
218
|
+
```
|
219
|
+
|
220
|
+
## Authentication
|
221
|
+
|
222
|
+
A builtin auth app is available.
|
223
|
+
|
224
|
+
Simply add `ohmyapi_auth` to your INSTALLED_APPS and define a JWT_SECRET in your `settings.py`.
|
225
|
+
Remember to `makemigrations` and `migrate` for the necessary tables to be created in the database.
|
226
|
+
|
227
|
+
`settings.py`:
|
228
|
+
|
229
|
+
```
|
230
|
+
INSTALLED_APPS = [
|
231
|
+
'ohmyapi_auth',
|
232
|
+
...
|
233
|
+
]
|
234
|
+
|
235
|
+
JWT_SECRET = "t0ps3cr3t"
|
236
|
+
```
|
237
|
+
|
238
|
+
After restarting your project you will have access to the `ohmyapi_auth` app.
|
239
|
+
It comes with a `User` and `Group` model, as well as endpoints for JWT auth.
|
240
|
+
|
241
|
+
You can use the models as `ForeignKeyField` in your application models:
|
242
|
+
|
243
|
+
```python
|
244
|
+
class Team(Model):
|
245
|
+
[...]
|
246
|
+
members = field.ManyToManyField('ohmyapi_auth.User', related_name='tournament_teams', through='tournament_teams')
|
247
|
+
[...]
|
248
|
+
```
|
249
|
+
|
250
|
+
Remember to run `makemigrations` and `migrate` in order for your model changes to take effect in the database.
|
251
|
+
|
252
|
+
Create a super-user:
|
253
|
+
|
254
|
+
```
|
255
|
+
ohmyapi createsuperuser
|
256
|
+
```
|
257
|
+
|
258
|
+
## Permissions
|
259
|
+
|
260
|
+
### API-Level Permissions
|
261
|
+
|
262
|
+
Use FastAPI's `Depends` pattern to implement API-level access-control.
|
263
|
+
|
264
|
+
|
265
|
+
In your `routes.py`:
|
266
|
+
|
267
|
+
```python
|
268
|
+
from ohmyapi.router import APIRouter, Depends
|
269
|
+
|
270
|
+
from ohmyapi_auth.models import User
|
271
|
+
from ohmyapi_auth import (
|
272
|
+
models as auth,
|
273
|
+
permissions,
|
274
|
+
)
|
275
|
+
|
276
|
+
from .models import Tournament
|
277
|
+
|
278
|
+
router = APIRouter(prefix="/tournament")
|
279
|
+
|
280
|
+
|
281
|
+
@router.get("/")
|
282
|
+
async def list(user: auth.User = Depends(permissions.require_authenticated)):
|
283
|
+
queryset = Tournament.all()
|
284
|
+
return await Tournament.Schema.many.from_queryset(queryset)
|
285
|
+
|
286
|
+
|
287
|
+
...
|
288
|
+
```
|
289
|
+
|
290
|
+
### Model-Level Permissions
|
291
|
+
|
292
|
+
Use Tortoise's `Manager` to implement model-layer permissions.
|
293
|
+
|
294
|
+
```python
|
295
|
+
from ohmyapi.db import Manager
|
296
|
+
from typing import Callable
|
297
|
+
|
298
|
+
|
299
|
+
class TeamManager(Manager):
|
300
|
+
async def for_user(self, user):
|
301
|
+
return await self.filter(members=user).all()
|
302
|
+
|
303
|
+
|
304
|
+
class Team(Model):
|
305
|
+
[...]
|
306
|
+
|
307
|
+
class Meta:
|
308
|
+
manager = TeamManager()
|
309
|
+
```
|
310
|
+
|
@@ -1,23 +1,25 @@
|
|
1
1
|
ohmyapi/__init__.py,sha256=UmLNQImTbKvHEgwQB2Wsyl6fq88X92imL9QZYJpQX4I,18
|
2
|
+
ohmyapi/__main__.py,sha256=wcCrL4PjG51r5wVKqJhcoJPTLfHW0wNbD31DrUN0MWI,28
|
2
3
|
ohmyapi/builtin/auth/__init__.py,sha256=TY1RKgwWmJ6FKz_v4J3m0Ang69qSmtVDLe4rqjLk4-E,69
|
3
4
|
ohmyapi/builtin/auth/models.py,sha256=PjmRGdQA2uHh3qYODi58WzRdfCwHN4x4r9vlVF6eCqQ,1512
|
4
5
|
ohmyapi/builtin/auth/permissions.py,sha256=gPBf01UNgXjU3v3DwMSYpKjayZSnwksp-ji4C99oX_I,111
|
5
6
|
ohmyapi/builtin/auth/routes.py,sha256=5jLq92z4TJw4eyiIJ5t7MwRNtYlpN7suDwW28JeEQYA,4902
|
6
|
-
ohmyapi/cli.py,sha256=
|
7
|
+
ohmyapi/cli.py,sha256=gJIchYR4N4bo65DNePF_Oisksm2du4oAkOktJUiY1a4,3449
|
7
8
|
ohmyapi/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
8
9
|
ohmyapi/core/runtime.py,sha256=l5zffc4VTwQOo7G9mfiYHsPGapMONRX_jtO_z9QaKHU,8577
|
9
10
|
ohmyapi/core/scaffolding.py,sha256=iMymscokJ-YqzB0ZTC-gcc2T71o73577j9tnb2x8lH8,2572
|
10
11
|
ohmyapi/core/templates/app/__init__.py.j2,sha256=QwVIQVUGZVhdH1d4NrvL7NTsK4-T4cihzYs8UVX2dt4,43
|
11
12
|
ohmyapi/core/templates/app/models.py.j2,sha256=_3w-vFJ5fgsmncsCv34k_wyCMF78jufbSSglns4gbb0,119
|
12
13
|
ohmyapi/core/templates/app/routes.py.j2,sha256=Ee3xeVdM_G5-TRw14_C6siJTrEXtnRUD5xhcHCbu5QA,229
|
13
|
-
ohmyapi/core/templates/project/
|
14
|
+
ohmyapi/core/templates/project/README.md.j2,sha256=SjR4JIrg-8XRE-UntUDwiw8jDpYitD_UjwoKkYJ7GLw,22
|
15
|
+
ohmyapi/core/templates/project/pyproject.toml.j2,sha256=X0VS6YT9aL3vpHFKPTfLFsdpD8423nY57ySQpSTMxmQ,895
|
14
16
|
ohmyapi/core/templates/project/settings.py.j2,sha256=RBKGB8MZWPM3Bp0a57Y1YrSvSXxh502TUnJqbbu48Ig,138
|
15
|
-
ohmyapi/db/__init__.py,sha256=
|
16
|
-
ohmyapi/db/
|
17
|
-
ohmyapi/db/model/__init__.py,sha256=
|
18
|
-
ohmyapi/db/model/model.py,sha256=
|
19
|
-
ohmyapi/router.py,sha256=
|
20
|
-
ohmyapi-0.1.
|
21
|
-
ohmyapi-0.1.
|
22
|
-
ohmyapi-0.1.
|
23
|
-
ohmyapi-0.1.
|
17
|
+
ohmyapi/db/__init__.py,sha256=PaDrioQ1lkFAo7Al82MoV6duQeJF0dTQ7EG0TzKOOIM,70
|
18
|
+
ohmyapi/db/exceptions.py,sha256=I7AubrdqQF_UvAvzKqz2ve08-BkXHzEWXnwG300StHE,35
|
19
|
+
ohmyapi/db/model/__init__.py,sha256=k3StTNuKatpwZo_Z5JBFa-927eJrzibFE8U4SA82asc,32
|
20
|
+
ohmyapi/db/model/model.py,sha256=BajFtLlQ1s0mZ2hj-_JNQhLQmxuVe-Lw2LuW5t2C7Rw,1579
|
21
|
+
ohmyapi/router.py,sha256=hutccsrP9RT8W5O6uBDhOJehwqrkRoPzaUI5zoHPh9A,55
|
22
|
+
ohmyapi-0.1.7.dist-info/METADATA,sha256=C2rchwDu2l4Qw4oSqIfOz3P07f7yV2RanYoBzCD2khg,6841
|
23
|
+
ohmyapi-0.1.7.dist-info/WHEEL,sha256=M5asmiAlL6HEcOq52Yi5mmk9KmTVjY2RDPtO4p9DMrc,88
|
24
|
+
ohmyapi-0.1.7.dist-info/entry_points.txt,sha256=wb3lw8-meAlpiv1mqcQ3m25ukL7djagU_w89GkrC37k,43
|
25
|
+
ohmyapi-0.1.7.dist-info/RECORD,,
|
ohmyapi/db/migration_manager.py
DELETED
@@ -1,90 +0,0 @@
|
|
1
|
-
import asyncio
|
2
|
-
from pathlib import Path
|
3
|
-
from aerich import Command
|
4
|
-
from ohmyapi.core import runtime
|
5
|
-
|
6
|
-
|
7
|
-
class MigrationManager:
|
8
|
-
def __init__(self, project):
|
9
|
-
self.project = project
|
10
|
-
self._commands = {}
|
11
|
-
# Compute tortoise_config grouped by app module
|
12
|
-
self._tortoise_config = self._build_tortoise_config()
|
13
|
-
|
14
|
-
def _build_tortoise_config(self) -> dict:
|
15
|
-
"""
|
16
|
-
Build Tortoise config from the flat model_registry,
|
17
|
-
grouping models by app module for Aerich compatibility.
|
18
|
-
"""
|
19
|
-
db_url = self.project.settings.DATABASE_URL
|
20
|
-
registry = self.project.model_registry # flat: model_path -> class
|
21
|
-
|
22
|
-
apps_modules = {}
|
23
|
-
for model_path, model_cls in registry.items():
|
24
|
-
if not isinstance(model_cls, type):
|
25
|
-
raise TypeError(f"Registry value must be a class, got {type(model_cls)}: {model_cls}")
|
26
|
-
# Extract app module by removing the model class name
|
27
|
-
# Example: 'ohmyapi.apps.auth.User' -> 'ohmyapi.apps.auth'
|
28
|
-
app_module = ".".join(model_path.split(".")[:-1])
|
29
|
-
apps_modules.setdefault(app_module, []).append(model_cls)
|
30
|
-
|
31
|
-
# Build Tortoise config
|
32
|
-
apps_config = {}
|
33
|
-
for app_module, models in apps_modules.items():
|
34
|
-
modules_set = set(m.__module__ for m in models)
|
35
|
-
apps_config[app_module] = {
|
36
|
-
"models": list(modules_set),
|
37
|
-
"default_connection": "default",
|
38
|
-
}
|
39
|
-
|
40
|
-
return {
|
41
|
-
"connections": {"default": db_url},
|
42
|
-
"apps": apps_config,
|
43
|
-
}
|
44
|
-
|
45
|
-
def get_apps(self):
|
46
|
-
"""Return app modules extracted from the registry"""
|
47
|
-
return list(self._tortoise_config["apps"].keys())
|
48
|
-
|
49
|
-
def get_migration_location(self, app_module: str) -> str:
|
50
|
-
"""Return the path to the app's migrations folder"""
|
51
|
-
try:
|
52
|
-
module = __import__(app_module, fromlist=["migrations"])
|
53
|
-
if not hasattr(module, "__file__") or module.__file__ is None:
|
54
|
-
raise ValueError(f"Cannot determine filesystem path for app '{app_module}'")
|
55
|
-
app_path = Path(module.__file__).parent
|
56
|
-
migrations_path = app_path / "migrations"
|
57
|
-
migrations_path.mkdir(exist_ok=True)
|
58
|
-
return str(migrations_path)
|
59
|
-
except ModuleNotFoundError:
|
60
|
-
raise ValueError(f"App module '{app_module}' cannot be imported")
|
61
|
-
|
62
|
-
async def init_app_command(self, app_module: str) -> Command:
|
63
|
-
"""Initialize Aerich command for a specific app module"""
|
64
|
-
location = self.get_migration_location(app_module)
|
65
|
-
cmd = Command(
|
66
|
-
tortoise_config=self._tortoise_config,
|
67
|
-
app=app_module,
|
68
|
-
location=location,
|
69
|
-
)
|
70
|
-
await cmd.init()
|
71
|
-
self._commands[app_module] = cmd
|
72
|
-
return cmd
|
73
|
-
|
74
|
-
async def makemigrations(self, app_module: str):
|
75
|
-
"""Generate migrations for a specific app"""
|
76
|
-
cmd = self._commands.get(app_module) or await self.init_app_command(app_module)
|
77
|
-
await cmd.migrate()
|
78
|
-
|
79
|
-
async def migrate(self, app_module: str = None):
|
80
|
-
"""Apply migrations. If app_module is None, migrate all apps"""
|
81
|
-
apps_to_migrate = [app_module] if app_module else self.get_apps()
|
82
|
-
for app in apps_to_migrate:
|
83
|
-
cmd = self._commands.get(app) or await self.init_app_command(app)
|
84
|
-
await cmd.upgrade()
|
85
|
-
|
86
|
-
async def show_migrations(self, app_module: str):
|
87
|
-
"""List migrations for an app"""
|
88
|
-
cmd = self._commands.get(app_module) or await self.init_app_command(app_module)
|
89
|
-
await cmd.history()
|
90
|
-
|
ohmyapi-0.1.5.dist-info/METADATA
DELETED
@@ -1,204 +0,0 @@
|
|
1
|
-
Metadata-Version: 2.4
|
2
|
-
Name: ohmyapi
|
3
|
-
Version: 0.1.5
|
4
|
-
Summary: A Django-like but async web-framework based on FastAPI and TortoiseORM.
|
5
|
-
License-Expression: MIT
|
6
|
-
Keywords: fastapi,tortoise,orm,async,web-framework
|
7
|
-
Author: Brian Wiborg
|
8
|
-
Author-email: me@brianwib.org
|
9
|
-
Requires-Python: >=3.13
|
10
|
-
Classifier: Programming Language :: Python :: 3
|
11
|
-
Classifier: Programming Language :: Python :: 3.13
|
12
|
-
Classifier: Programming Language :: Python :: 3.14
|
13
|
-
Requires-Dist: aerich (>=0.9.1,<0.10.0)
|
14
|
-
Requires-Dist: argon2-cffi (>=25.1.0,<26.0.0)
|
15
|
-
Requires-Dist: crypto (>=1.4.1,<2.0.0)
|
16
|
-
Requires-Dist: fastapi (>=0.117.1,<0.118.0)
|
17
|
-
Requires-Dist: ipython (>=9.5.0,<10.0.0)
|
18
|
-
Requires-Dist: jinja2 (>=3.1.6,<4.0.0)
|
19
|
-
Requires-Dist: passlib (>=1.7.4,<2.0.0)
|
20
|
-
Requires-Dist: pyjwt (>=2.10.1,<3.0.0)
|
21
|
-
Requires-Dist: python-multipart (>=0.0.20,<0.0.21)
|
22
|
-
Requires-Dist: tortoise-orm (>=0.25.1,<0.26.0)
|
23
|
-
Requires-Dist: typer (>=0.19.1,<0.20.0)
|
24
|
-
Requires-Dist: uvicorn (>=0.36.0,<0.37.0)
|
25
|
-
Description-Content-Type: text/markdown
|
26
|
-
|
27
|
-
# OhMyAPI
|
28
|
-
|
29
|
-
> OhMyAPI == Application scaffolding for FastAPI+TortoiseORM.
|
30
|
-
|
31
|
-
OhMyAPI is a Django-flavored web-application scaffolding framework.
|
32
|
-
Built around FastAPI and TortoiseORM, it 100% async.
|
33
|
-
It is blazingly fast and has batteries included.
|
34
|
-
|
35
|
-
Features:
|
36
|
-
|
37
|
-
- Django-like project-layout and -structure
|
38
|
-
- Django-like settings.py
|
39
|
-
- Django-like models via TortoiseORM
|
40
|
-
- Django-like model.Meta class for model configuration
|
41
|
-
- Django-like advanced permissions system
|
42
|
-
- Django-like migrations (makemigrations & migrate) via Aerich
|
43
|
-
- Django-like CLI for interfacing with your projects (startproject, startapp, shell, serve, etc)
|
44
|
-
- various optional builtin apps
|
45
|
-
- highly configurable and customizable
|
46
|
-
- 100% async
|
47
|
-
|
48
|
-
## Getting started
|
49
|
-
|
50
|
-
**Creating a Project**
|
51
|
-
|
52
|
-
```
|
53
|
-
pip install ohmyapi
|
54
|
-
ohmyapi startproject myproject
|
55
|
-
cd myproject
|
56
|
-
```
|
57
|
-
|
58
|
-
This will create the following directory structure:
|
59
|
-
|
60
|
-
```
|
61
|
-
myproject/
|
62
|
-
- pyproject.toml
|
63
|
-
- settings.py
|
64
|
-
```
|
65
|
-
|
66
|
-
Run your project with:
|
67
|
-
|
68
|
-
```
|
69
|
-
ohmyapi serve
|
70
|
-
```
|
71
|
-
|
72
|
-
In your browser go to:
|
73
|
-
- http://localhost:8000/docs
|
74
|
-
|
75
|
-
**Creating an App**
|
76
|
-
|
77
|
-
Create a new app by:
|
78
|
-
|
79
|
-
```
|
80
|
-
ohmyapi startapp myapp
|
81
|
-
```
|
82
|
-
|
83
|
-
This will lead to the following directory structure:
|
84
|
-
|
85
|
-
```
|
86
|
-
myproject/
|
87
|
-
- myapp/
|
88
|
-
- __init__.py
|
89
|
-
- models.py
|
90
|
-
- routes.py
|
91
|
-
- pyproject.toml
|
92
|
-
- settings.py
|
93
|
-
```
|
94
|
-
|
95
|
-
Add 'myapp' to your `INSTALLED_APPS` in `settings.py`.
|
96
|
-
|
97
|
-
Write your first model in `myapp/models.py`:
|
98
|
-
|
99
|
-
```python
|
100
|
-
from ohmyapi.db import Model, field
|
101
|
-
|
102
|
-
|
103
|
-
class Person(Model):
|
104
|
-
id: int = field.IntField(min=1, pk=True)
|
105
|
-
name: str = field.CharField(min_length=1, max_length=255)
|
106
|
-
username: str = field.CharField(min_length=1, max_length=255, unique=True)
|
107
|
-
age: int = field.IntField(min=0)
|
108
|
-
```
|
109
|
-
|
110
|
-
Next, create your endpoints in `myapp/routes.py`:
|
111
|
-
|
112
|
-
```python
|
113
|
-
from fastapi import APIRouter, HTTPException
|
114
|
-
from tortoise.exceptions import DoesNotExist
|
115
|
-
|
116
|
-
from .models import Person
|
117
|
-
|
118
|
-
router = APIRouter(prefix="/myapp")
|
119
|
-
|
120
|
-
|
121
|
-
@router.get("/")
|
122
|
-
async def list():
|
123
|
-
return await Person.Schema.many.from_queryset(Person.all())
|
124
|
-
|
125
|
-
|
126
|
-
@router.get("/:id")
|
127
|
-
async def get(id: int):
|
128
|
-
try:
|
129
|
-
return await Person.Schema.one(Person.get(pk=id))
|
130
|
-
except DoesNotExist:
|
131
|
-
raise HTTPException(status_code=404, detail="item not found")
|
132
|
-
|
133
|
-
...
|
134
|
-
```
|
135
|
-
|
136
|
-
## Migrations
|
137
|
-
|
138
|
-
Before we can run the app, we need to create and initialize the database.
|
139
|
-
|
140
|
-
Similar to Django, first run:
|
141
|
-
|
142
|
-
```
|
143
|
-
ohmyapi makemigrations [ <app> ] # no app means all INSTALLED_APPS
|
144
|
-
```
|
145
|
-
|
146
|
-
This will create a `migrations/` folder in you project root.
|
147
|
-
|
148
|
-
```
|
149
|
-
myproject/
|
150
|
-
- myapp/
|
151
|
-
- __init__.py
|
152
|
-
- models.py
|
153
|
-
- routes.py
|
154
|
-
- migrations/
|
155
|
-
- myapp/
|
156
|
-
- pyproject.toml
|
157
|
-
- settings.py
|
158
|
-
```
|
159
|
-
|
160
|
-
Apply your migrations via:
|
161
|
-
|
162
|
-
```
|
163
|
-
ohmyapi migrate [ <app> ] # no app means all INSTALLED_APPS
|
164
|
-
```
|
165
|
-
|
166
|
-
Run your project:
|
167
|
-
|
168
|
-
```
|
169
|
-
ohmyapi serve
|
170
|
-
```
|
171
|
-
|
172
|
-
## Shell
|
173
|
-
|
174
|
-
Similar to Django, you can attach to an interactive shell with your project already loaded inside.
|
175
|
-
|
176
|
-
```
|
177
|
-
ohmyapi shell
|
178
|
-
```
|
179
|
-
|
180
|
-
## Authentication
|
181
|
-
|
182
|
-
A builtin auth app is available.
|
183
|
-
|
184
|
-
Simply add `ohmyapi_auth` to your INSTALLED_APPS and define a JWT_SECRET in your `settings.py`.
|
185
|
-
Remember to `makemigrations` and `migrate` for the auth tables to be created in the database.
|
186
|
-
|
187
|
-
`settings.py`:
|
188
|
-
|
189
|
-
```
|
190
|
-
INSTALLED_APPS = [
|
191
|
-
'ohmyapi_auth',
|
192
|
-
...
|
193
|
-
]
|
194
|
-
|
195
|
-
JWT_SECRET = "t0ps3cr3t"
|
196
|
-
```
|
197
|
-
|
198
|
-
Create a super-user:
|
199
|
-
|
200
|
-
```
|
201
|
-
ohmyapi createsuperuser
|
202
|
-
```
|
203
|
-
|
204
|
-
|
File without changes
|