ohmyapi 0.1.6__tar.gz → 0.1.8__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 (31) hide show
  1. ohmyapi-0.1.8/PKG-INFO +316 -0
  2. ohmyapi-0.1.8/README.md +283 -0
  3. ohmyapi-0.1.8/pyproject.toml +38 -0
  4. ohmyapi-0.1.8/src/ohmyapi/__main__.py +3 -0
  5. {ohmyapi-0.1.6 → ohmyapi-0.1.8}/src/ohmyapi/builtin/auth/models.py +2 -2
  6. {ohmyapi-0.1.6 → ohmyapi-0.1.8}/src/ohmyapi/builtin/auth/permissions.py +1 -0
  7. {ohmyapi-0.1.6 → ohmyapi-0.1.8}/src/ohmyapi/builtin/auth/routes.py +46 -14
  8. {ohmyapi-0.1.6 → ohmyapi-0.1.8}/src/ohmyapi/cli.py +2 -4
  9. ohmyapi-0.1.8/src/ohmyapi/core/templates/project/README.md.j2 +2 -0
  10. ohmyapi-0.1.8/src/ohmyapi/core/templates/project/pyproject.toml.j2 +37 -0
  11. ohmyapi-0.1.8/src/ohmyapi/db/__init__.py +3 -0
  12. ohmyapi-0.1.8/src/ohmyapi/db/exceptions.py +2 -0
  13. ohmyapi-0.1.8/src/ohmyapi/db/model/__init__.py +1 -0
  14. {ohmyapi-0.1.6 → ohmyapi-0.1.8}/src/ohmyapi/db/model/model.py +1 -1
  15. ohmyapi-0.1.8/src/ohmyapi/router.py +2 -0
  16. ohmyapi-0.1.6/PKG-INFO +0 -204
  17. ohmyapi-0.1.6/README.md +0 -177
  18. ohmyapi-0.1.6/pyproject.toml +0 -36
  19. ohmyapi-0.1.6/src/ohmyapi/core/templates/project/pyproject.toml.j2 +0 -13
  20. ohmyapi-0.1.6/src/ohmyapi/db/__init__.py +0 -3
  21. ohmyapi-0.1.6/src/ohmyapi/db/model/__init__.py +0 -1
  22. ohmyapi-0.1.6/src/ohmyapi/router.py +0 -2
  23. {ohmyapi-0.1.6 → ohmyapi-0.1.8}/src/ohmyapi/__init__.py +0 -0
  24. {ohmyapi-0.1.6 → ohmyapi-0.1.8}/src/ohmyapi/builtin/auth/__init__.py +0 -0
  25. {ohmyapi-0.1.6 → ohmyapi-0.1.8}/src/ohmyapi/core/__init__.py +0 -0
  26. {ohmyapi-0.1.6 → ohmyapi-0.1.8}/src/ohmyapi/core/runtime.py +0 -0
  27. {ohmyapi-0.1.6 → ohmyapi-0.1.8}/src/ohmyapi/core/scaffolding.py +0 -0
  28. {ohmyapi-0.1.6 → ohmyapi-0.1.8}/src/ohmyapi/core/templates/app/__init__.py.j2 +0 -0
  29. {ohmyapi-0.1.6 → ohmyapi-0.1.8}/src/ohmyapi/core/templates/app/models.py.j2 +0 -0
  30. {ohmyapi-0.1.6 → ohmyapi-0.1.8}/src/ohmyapi/core/templates/app/routes.py.j2 +0 -0
  31. {ohmyapi-0.1.6 → ohmyapi-0.1.8}/src/ohmyapi/core/templates/project/settings.py.j2 +0 -0
ohmyapi-0.1.8/PKG-INFO ADDED
@@ -0,0 +1,316 @@
1
+ Metadata-Version: 2.4
2
+ Name: ohmyapi
3
+ Version: 0.1.8
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: Django RestFramework, but 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 project-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
+ OhMyAPI aims to:
56
+
57
+ - combine FastAPI, TortoiseORM and Aerich migrations into a high-productivity web-application framework
58
+ - tying everything neatly together into a project structure consisting of apps with models and a router
59
+ - while ***AVOIDING*** to introduce any additional abstractions ontop of Tortoise's model-system or FastAPI's routing
60
+
61
+ ---
62
+
63
+ ## Getting started
64
+
65
+ **Creating a Project**
66
+
67
+ ```
68
+ pip install ohmyapi
69
+ ohmyapi startproject myproject
70
+ cd myproject
71
+ ```
72
+
73
+ This will create the following directory structure:
74
+
75
+ ```
76
+ myproject/
77
+ - pyproject.toml
78
+ - README.md
79
+ - settings.py
80
+ ```
81
+
82
+ Run your project with:
83
+
84
+ ```
85
+ ohmyapi serve
86
+ ```
87
+
88
+ In your browser go to:
89
+ - http://localhost:8000/docs
90
+
91
+ **Creating an App**
92
+
93
+ Create a new app by:
94
+
95
+ ```
96
+ ohmyapi startapp tournament
97
+ ```
98
+
99
+ This will create the following directory structure:
100
+
101
+ ```
102
+ myproject/
103
+ - tournament/
104
+ - __init__.py
105
+ - models.py
106
+ - routes.py
107
+ - pyproject.toml
108
+ - README.md
109
+ - settings.py
110
+ ```
111
+
112
+ Add 'tournament' to your `INSTALLED_APPS` in `settings.py`.
113
+
114
+ ### Models
115
+
116
+ Write your first model in `turnament/models.py`:
117
+
118
+ ```python
119
+ from ohmyapi.db import Model, field
120
+
121
+
122
+ class Tournament(Model):
123
+ id = field.data.UUIDField(primary_key=True)
124
+ name = field.TextField()
125
+ created = field.DatetimeField(auto_now_add=True)
126
+
127
+ def __str__(self):
128
+ return self.name
129
+
130
+
131
+ class Event(Model):
132
+ id = field.data.UUIDField(primary_key=True)
133
+ name = field.TextField()
134
+ tournament = field.ForeignKeyField('tournament.Tournament', related_name='events')
135
+ participants = field.ManyToManyField('torunament.Team', related_name='events', through='event_team')
136
+ modified = field.DatetimeField(auto_now=True)
137
+ prize = field.DecimalField(max_digits=10, decimal_places=2, null=True)
138
+
139
+ def __str__(self):
140
+ return self.name
141
+
142
+
143
+ class Team(Model):
144
+ id = field.data.UUIDField(primary_key=True)
145
+ name = field.TextField()
146
+
147
+ def __str__(self):
148
+ return self.name
149
+ ```
150
+
151
+ ### API Routes
152
+
153
+ Next, create your endpoints in `tournament/routes.py`:
154
+
155
+ ```python
156
+ from ohmyapi.router import APIRouter, HTTPException
157
+ from ohmyapi.db.exceptions import DoesNotExist
158
+
159
+ from .models import Tournament
160
+
161
+ router = APIRouter(prefix="/tournament")
162
+
163
+
164
+ @router.get("/")
165
+ async def list():
166
+ queryset = Tournament.all()
167
+ return await Tournament.Schema.many.from_queryset(queryset)
168
+
169
+
170
+ @router.get("/:id")
171
+ async def get(id: str):
172
+ try:
173
+ queryset = Tournament.get(pk=id)
174
+ return await Tournament.Schema.one(queryset)
175
+ except DoesNotExist:
176
+ raise HTTPException(status_code=404, detail="item not found")
177
+
178
+ ...
179
+ ```
180
+
181
+ ## Migrations
182
+
183
+ Before we can run the app, we need to create and initialize the database.
184
+
185
+ Similar to Django, first run:
186
+
187
+ ```
188
+ ohmyapi makemigrations [ <app> ] # no app means all INSTALLED_APPS
189
+ ```
190
+
191
+ This will create a `migrations/` folder in you project root.
192
+
193
+ ```
194
+ myproject/
195
+ - tournament/
196
+ - __init__.py
197
+ - models.py
198
+ - routes.py
199
+ - migrations/
200
+ - tournament/
201
+ - pyproject.toml
202
+ - README.md
203
+ - settings.py
204
+ ```
205
+
206
+ Apply your migrations via:
207
+
208
+ ```
209
+ ohmyapi migrate [ <app> ] # no app means all INSTALLED_APPS
210
+ ```
211
+
212
+ Run your project:
213
+
214
+ ```
215
+ ohmyapi serve
216
+ ```
217
+
218
+ ## Shell
219
+
220
+ Similar to Django, you can attach to an interactive shell with your project already loaded inside.
221
+
222
+ ```
223
+ ohmyapi shell
224
+ ```
225
+
226
+ ## Authentication
227
+
228
+ A builtin auth app is available.
229
+
230
+ Simply add `ohmyapi_auth` to your INSTALLED_APPS and define a JWT_SECRET in your `settings.py`.
231
+ Remember to `makemigrations` and `migrate` for the necessary tables to be created in the database.
232
+
233
+ `settings.py`:
234
+
235
+ ```
236
+ INSTALLED_APPS = [
237
+ 'ohmyapi_auth',
238
+ ...
239
+ ]
240
+
241
+ JWT_SECRET = "t0ps3cr3t"
242
+ ```
243
+
244
+ After restarting your project you will have access to the `ohmyapi_auth` app.
245
+ It comes with a `User` and `Group` model, as well as endpoints for JWT auth.
246
+
247
+ You can use the models as `ForeignKeyField` in your application models:
248
+
249
+ ```python
250
+ class Team(Model):
251
+ [...]
252
+ members = field.ManyToManyField('ohmyapi_auth.User', related_name='tournament_teams', through='tournament_teams')
253
+ [...]
254
+ ```
255
+
256
+ Remember to run `makemigrations` and `migrate` in order for your model changes to take effect in the database.
257
+
258
+ Create a super-user:
259
+
260
+ ```
261
+ ohmyapi createsuperuser
262
+ ```
263
+
264
+ ## Permissions
265
+
266
+ ### API-Level Permissions
267
+
268
+ Use FastAPI's `Depends` pattern to implement API-level access-control.
269
+
270
+
271
+ In your `routes.py`:
272
+
273
+ ```python
274
+ from ohmyapi.router import APIRouter, Depends
275
+
276
+ from ohmyapi_auth.models import User
277
+ from ohmyapi_auth import (
278
+ models as auth,
279
+ permissions,
280
+ )
281
+
282
+ from .models import Tournament
283
+
284
+ router = APIRouter(prefix="/tournament")
285
+
286
+
287
+ @router.get("/")
288
+ async def list(user: auth.User = Depends(permissions.require_authenticated)):
289
+ queryset = Tournament.all()
290
+ return await Tournament.Schema.many.from_queryset(queryset)
291
+
292
+
293
+ ...
294
+ ```
295
+
296
+ ### Model-Level Permissions
297
+
298
+ Use Tortoise's `Manager` to implement model-level permissions.
299
+
300
+ ```python
301
+ from ohmyapi.db import Manager
302
+ from typing import Callable
303
+
304
+
305
+ class TeamManager(Manager):
306
+ async def for_user(self, user: ohmyapi_auth.models.User):
307
+ return await self.filter(members=user).all()
308
+
309
+
310
+ class Team(Model):
311
+ [...]
312
+
313
+ class Meta:
314
+ manager = TeamManager()
315
+ ```
316
+
@@ -0,0 +1,283 @@
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, it is 100% async.
7
+
8
+ It is ***blazingly fast***, ***fun to use*** and comes with ***batteries included***!
9
+
10
+ **Features**
11
+
12
+ - Django-like project-layout and -structure
13
+ - Django-like project-level settings.py
14
+ - Django-like models via TortoiseORM
15
+ - Django-like `Model.Meta` class for model configuration
16
+ - Easily convert your query results to `pydantic` models via `Model.Schema`
17
+ - Django-like migrations (`makemigrations` & `migrate`) via Aerich
18
+ - Django-like CLI tooling (`startproject`, `startapp`, `shell`, `serve`, etc)
19
+ - Various optional builtin apps you can hook into your project
20
+ - Highly configurable and customizable
21
+ - 100% async
22
+
23
+ OhMyAPI aims to:
24
+
25
+ - combine FastAPI, TortoiseORM and Aerich migrations into a high-productivity web-application framework
26
+ - tying everything neatly together into a project structure consisting of apps with models and a router
27
+ - while ***AVOIDING*** to introduce any additional abstractions ontop of Tortoise's model-system or FastAPI's routing
28
+
29
+ ---
30
+
31
+ ## Getting started
32
+
33
+ **Creating a Project**
34
+
35
+ ```
36
+ pip install ohmyapi
37
+ ohmyapi startproject myproject
38
+ cd myproject
39
+ ```
40
+
41
+ This will create the following directory structure:
42
+
43
+ ```
44
+ myproject/
45
+ - pyproject.toml
46
+ - README.md
47
+ - settings.py
48
+ ```
49
+
50
+ Run your project with:
51
+
52
+ ```
53
+ ohmyapi serve
54
+ ```
55
+
56
+ In your browser go to:
57
+ - http://localhost:8000/docs
58
+
59
+ **Creating an App**
60
+
61
+ Create a new app by:
62
+
63
+ ```
64
+ ohmyapi startapp tournament
65
+ ```
66
+
67
+ This will create the following directory structure:
68
+
69
+ ```
70
+ myproject/
71
+ - tournament/
72
+ - __init__.py
73
+ - models.py
74
+ - routes.py
75
+ - pyproject.toml
76
+ - README.md
77
+ - settings.py
78
+ ```
79
+
80
+ Add 'tournament' to your `INSTALLED_APPS` in `settings.py`.
81
+
82
+ ### Models
83
+
84
+ Write your first model in `turnament/models.py`:
85
+
86
+ ```python
87
+ from ohmyapi.db import Model, field
88
+
89
+
90
+ class Tournament(Model):
91
+ id = field.data.UUIDField(primary_key=True)
92
+ name = field.TextField()
93
+ created = field.DatetimeField(auto_now_add=True)
94
+
95
+ def __str__(self):
96
+ return self.name
97
+
98
+
99
+ class Event(Model):
100
+ id = field.data.UUIDField(primary_key=True)
101
+ name = field.TextField()
102
+ tournament = field.ForeignKeyField('tournament.Tournament', related_name='events')
103
+ participants = field.ManyToManyField('torunament.Team', related_name='events', through='event_team')
104
+ modified = field.DatetimeField(auto_now=True)
105
+ prize = field.DecimalField(max_digits=10, decimal_places=2, null=True)
106
+
107
+ def __str__(self):
108
+ return self.name
109
+
110
+
111
+ class Team(Model):
112
+ id = field.data.UUIDField(primary_key=True)
113
+ name = field.TextField()
114
+
115
+ def __str__(self):
116
+ return self.name
117
+ ```
118
+
119
+ ### API Routes
120
+
121
+ Next, create your endpoints in `tournament/routes.py`:
122
+
123
+ ```python
124
+ from ohmyapi.router import APIRouter, HTTPException
125
+ from ohmyapi.db.exceptions import DoesNotExist
126
+
127
+ from .models import Tournament
128
+
129
+ router = APIRouter(prefix="/tournament")
130
+
131
+
132
+ @router.get("/")
133
+ async def list():
134
+ queryset = Tournament.all()
135
+ return await Tournament.Schema.many.from_queryset(queryset)
136
+
137
+
138
+ @router.get("/:id")
139
+ async def get(id: str):
140
+ try:
141
+ queryset = Tournament.get(pk=id)
142
+ return await Tournament.Schema.one(queryset)
143
+ except DoesNotExist:
144
+ raise HTTPException(status_code=404, detail="item not found")
145
+
146
+ ...
147
+ ```
148
+
149
+ ## Migrations
150
+
151
+ Before we can run the app, we need to create and initialize the database.
152
+
153
+ Similar to Django, first run:
154
+
155
+ ```
156
+ ohmyapi makemigrations [ <app> ] # no app means all INSTALLED_APPS
157
+ ```
158
+
159
+ This will create a `migrations/` folder in you project root.
160
+
161
+ ```
162
+ myproject/
163
+ - tournament/
164
+ - __init__.py
165
+ - models.py
166
+ - routes.py
167
+ - migrations/
168
+ - tournament/
169
+ - pyproject.toml
170
+ - README.md
171
+ - settings.py
172
+ ```
173
+
174
+ Apply your migrations via:
175
+
176
+ ```
177
+ ohmyapi migrate [ <app> ] # no app means all INSTALLED_APPS
178
+ ```
179
+
180
+ Run your project:
181
+
182
+ ```
183
+ ohmyapi serve
184
+ ```
185
+
186
+ ## Shell
187
+
188
+ Similar to Django, you can attach to an interactive shell with your project already loaded inside.
189
+
190
+ ```
191
+ ohmyapi shell
192
+ ```
193
+
194
+ ## Authentication
195
+
196
+ A builtin auth app is available.
197
+
198
+ Simply add `ohmyapi_auth` to your INSTALLED_APPS and define a JWT_SECRET in your `settings.py`.
199
+ Remember to `makemigrations` and `migrate` for the necessary tables to be created in the database.
200
+
201
+ `settings.py`:
202
+
203
+ ```
204
+ INSTALLED_APPS = [
205
+ 'ohmyapi_auth',
206
+ ...
207
+ ]
208
+
209
+ JWT_SECRET = "t0ps3cr3t"
210
+ ```
211
+
212
+ After restarting your project you will have access to the `ohmyapi_auth` app.
213
+ It comes with a `User` and `Group` model, as well as endpoints for JWT auth.
214
+
215
+ You can use the models as `ForeignKeyField` in your application models:
216
+
217
+ ```python
218
+ class Team(Model):
219
+ [...]
220
+ members = field.ManyToManyField('ohmyapi_auth.User', related_name='tournament_teams', through='tournament_teams')
221
+ [...]
222
+ ```
223
+
224
+ Remember to run `makemigrations` and `migrate` in order for your model changes to take effect in the database.
225
+
226
+ Create a super-user:
227
+
228
+ ```
229
+ ohmyapi createsuperuser
230
+ ```
231
+
232
+ ## Permissions
233
+
234
+ ### API-Level Permissions
235
+
236
+ Use FastAPI's `Depends` pattern to implement API-level access-control.
237
+
238
+
239
+ In your `routes.py`:
240
+
241
+ ```python
242
+ from ohmyapi.router import APIRouter, Depends
243
+
244
+ from ohmyapi_auth.models import User
245
+ from ohmyapi_auth import (
246
+ models as auth,
247
+ permissions,
248
+ )
249
+
250
+ from .models import Tournament
251
+
252
+ router = APIRouter(prefix="/tournament")
253
+
254
+
255
+ @router.get("/")
256
+ async def list(user: auth.User = Depends(permissions.require_authenticated)):
257
+ queryset = Tournament.all()
258
+ return await Tournament.Schema.many.from_queryset(queryset)
259
+
260
+
261
+ ...
262
+ ```
263
+
264
+ ### Model-Level Permissions
265
+
266
+ Use Tortoise's `Manager` to implement model-level permissions.
267
+
268
+ ```python
269
+ from ohmyapi.db import Manager
270
+ from typing import Callable
271
+
272
+
273
+ class TeamManager(Manager):
274
+ async def for_user(self, user: ohmyapi_auth.models.User):
275
+ return await self.filter(members=user).all()
276
+
277
+
278
+ class Team(Model):
279
+ [...]
280
+
281
+ class Meta:
282
+ manager = TeamManager()
283
+ ```
@@ -0,0 +1,38 @@
1
+ [project]
2
+ name = "ohmyapi"
3
+ version = "0.1.8"
4
+ description = "A Django-like but async web-framework based on FastAPI and TortoiseORM."
5
+ license = "MIT"
6
+ keywords = ["fastapi", "tortoise", "orm", "async", "web-framework"]
7
+ authors = [
8
+ {name = "Brian Wiborg", email = "me@brianwib.org"}
9
+ ]
10
+ readme = "README.md"
11
+ requires-python = ">=3.13"
12
+
13
+ dependencies = [
14
+ "typer >=0.19.1,<0.20.0",
15
+ "jinja2 >=3.1.6,<4.0.0",
16
+ "fastapi >=0.117.1,<0.118.0",
17
+ "tortoise-orm >=0.25.1,<0.26.0",
18
+ "aerich >=0.9.1,<0.10.0",
19
+ "uvicorn >=0.36.0,<0.37.0",
20
+ "ipython >=9.5.0,<10.0.0",
21
+ "passlib >=1.7.4,<2.0.0",
22
+ "pyjwt >=2.10.1,<3.0.0",
23
+ "python-multipart >=0.0.20,<0.0.21",
24
+ "crypto >=1.4.1,<2.0.0",
25
+ "argon2-cffi >=25.1.0,<26.0.0",
26
+ ]
27
+
28
+ [tool.poetry.group.dev.dependencies]
29
+ ipython = ">=9.5.0,<10.0.0"
30
+
31
+ [project.optional-dependencies]
32
+ auth = ["passlib", "pyjwt", "crypto", "argon2-cffi", "python-multipart"]
33
+
34
+ [tool.poetry]
35
+ packages = [ { include = "ohmyapi", from = "src" } ]
36
+
37
+ [project.scripts]
38
+ ohmyapi = "ohmyapi.cli:app"
@@ -0,0 +1,3 @@
1
+ from .cli import app
2
+
3
+ app()
@@ -7,12 +7,12 @@ pwd_context = CryptContext(schemes=["argon2"], deprecated="auto")
7
7
 
8
8
 
9
9
  class Group(Model):
10
- id = field.IntField(pk=True)
10
+ id = field.data.UUIDField(pk=True)
11
11
  name = field.CharField(max_length=42, index=True)
12
12
 
13
13
 
14
14
  class User(Model):
15
- id = field.IntField(pk=True)
15
+ id = field.data.UUIDField(pk=True)
16
16
  email = field.CharField(max_length=255, unique=True, index=True)
17
17
  username = field.CharField(max_length=150, unique=True)
18
18
  password_hash = field.CharField(max_length=128)
@@ -1,4 +1,5 @@
1
1
  from .routes import (
2
+ get_token,
2
3
  get_current_user,
3
4
  require_authenticated,
4
5
  require_admin,
@@ -1,12 +1,13 @@
1
1
  import time
2
- from typing import Dict
2
+ from enum import Enum
3
+ from typing import Any, Dict, List
3
4
 
4
5
  import jwt
5
6
  from fastapi import APIRouter, Body, Depends, Header, HTTPException, status
6
7
  from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
7
8
  from pydantic import BaseModel
8
9
 
9
- from ohmyapi.builtin.auth.models import User
10
+ from ohmyapi.builtin.auth.models import User, Group
10
11
 
11
12
  import settings
12
13
 
@@ -40,14 +41,39 @@ def decode_token(token: str) -> Dict:
40
41
  raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token")
41
42
 
42
43
 
44
+ class TokenType(str, Enum):
45
+ """
46
+ Helper for indicating the token type when generating claims.
47
+ """
48
+ access = "access"
49
+ refresh = "refresh"
50
+
51
+
52
+ def claims(token_type: TokenType, user: User, groups: List[Group] = []) -> Dict[str, Any]:
53
+ return {
54
+ 'type': token_type,
55
+ 'sub': str(user.id),
56
+ 'user': {
57
+ 'username': user.username,
58
+ 'email': user.email,
59
+ },
60
+ 'roles': [g.name for g in groups]
61
+ }
62
+
63
+ async def get_token(token: str = Depends(oauth2_scheme)) -> Dict:
64
+ """Dependency: token introspection"""
65
+ payload = decode_token(token)
66
+ return payload
67
+
68
+
43
69
  async def get_current_user(token: str = Depends(oauth2_scheme)) -> User:
44
70
  """Dependency: extract user from access token."""
45
71
  payload = decode_token(token)
46
- username = payload.get("sub")
47
- if username is None:
72
+ user_id = payload.get("sub")
73
+ if user_id is None:
48
74
  raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token payload")
49
75
 
50
- user = await User.filter(username=username).first()
76
+ user = await User.filter(id=user_id).first()
51
77
  if not user:
52
78
  raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="User not found")
53
79
  return user
@@ -100,8 +126,8 @@ async def login(form_data: LoginRequest = Body(...)):
100
126
  if not user:
101
127
  raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid credentials")
102
128
 
103
- access_token = create_token({"sub": user.username, "type": "access"}, ACCESS_TOKEN_EXPIRE_SECONDS)
104
- refresh_token = create_token({"sub": user.username, "type": "refresh"}, REFRESH_TOKEN_EXPIRE_SECONDS)
129
+ access_token = create_token(claims(TokenType.access, user), ACCESS_TOKEN_EXPIRE_SECONDS)
130
+ refresh_token = create_token(claims(TokenType.refresh, user), REFRESH_TOKEN_EXPIRE_SECONDS)
105
131
 
106
132
  return {
107
133
  "access_token": access_token,
@@ -117,20 +143,26 @@ async def refresh_token(refresh_token: str):
117
143
  if payload.get("type") != "refresh":
118
144
  raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid refresh token")
119
145
 
120
- username = payload.get("sub")
121
- user = await User.filter(username=username).first()
146
+ user_id = payload.get("sub")
147
+ user = await User.filter(id=user_id).first()
122
148
  if not user:
123
149
  raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="User not found")
124
150
 
125
- new_access = create_token({"sub": user.username, "type": "access"}, ACCESS_TOKEN_EXPIRE_SECONDS)
151
+ new_access = create_token(claims(TokenType.access, user), ACCESS_TOKEN_EXPIRE_SECONDS)
126
152
  return {"access_token": new_access, "token_type": "bearer"}
127
153
 
128
154
 
129
155
  @router.get("/me")
130
- async def me(current_user: User = Depends(get_current_user)):
156
+ async def me(user: User = Depends(get_current_user)):
131
157
  """Return the currently authenticated user."""
132
158
  return {
133
- "username": current_user.username,
134
- "is_admin": current_user.is_admin,
135
- "is_staff": current_user.is_staff,
159
+ "email": user.email,
160
+ "username": user.username,
161
+ "is_admin": user.is_admin,
162
+ "is_staff": user.is_staff,
136
163
  }
164
+
165
+
166
+ @router.get("/introspect")
167
+ async def introspect(token: Dict = Depends(get_token)):
168
+ return token
@@ -101,14 +101,12 @@ def createsuperuser(root: str = "."):
101
101
 
102
102
  import asyncio
103
103
  import ohmyapi_auth
104
+ email = input("E-Mail: ")
104
105
  username = input("Username: ")
105
106
  password = getpass("Password: ")
106
- user = ohmyapi_auth.models.User(username=username, is_staff=True, is_admin=True)
107
+ user = ohmyapi_auth.models.User(email=email, username=username, is_staff=True, is_admin=True)
107
108
  user.set_password(password)
108
109
  asyncio.run(project.init_orm())
109
110
  asyncio.run(user.save())
110
111
  asyncio.run(project.close_orm())
111
112
 
112
- def main():
113
- app()
114
-
@@ -0,0 +1,2 @@
1
+ # {{ project_name }}
2
+
@@ -0,0 +1,37 @@
1
+ [project]
2
+ name = "{{ project_name }}"
3
+ version = "0.1.0"
4
+ description = "OhMyAPI project"
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"
29
+
30
+ [project.optional-dependencies]
31
+ auth = ["passlib", "pyjwt", "crypto", "argon2-cffi", "python-multipart"]
32
+
33
+ [tool.poetry]
34
+ package-mode = false
35
+
36
+ [project.scripts]
37
+ {{ project_name }} = "ohmyapi.cli:app"
@@ -0,0 +1,3 @@
1
+ from .model import Model, field
2
+ from tortoise.manager import Manager
3
+
@@ -0,0 +1,2 @@
1
+ from tortoise.exceptions import *
2
+
@@ -0,0 +1 @@
1
+ from .model import Model, field
@@ -1,4 +1,4 @@
1
- from tortoise import fields
1
+ from tortoise import fields as field
2
2
  from tortoise.models import Model as TortoiseModel
3
3
  from tortoise.contrib.pydantic import pydantic_model_creator, pydantic_queryset_creator
4
4
 
@@ -0,0 +1,2 @@
1
+ from fastapi import APIRouter, Depends, HTTPException
2
+
ohmyapi-0.1.6/PKG-INFO DELETED
@@ -1,204 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: ohmyapi
3
- Version: 0.1.6
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
-
ohmyapi-0.1.6/README.md DELETED
@@ -1,177 +0,0 @@
1
- # OhMyAPI
2
-
3
- > OhMyAPI == Application scaffolding for FastAPI+TortoiseORM.
4
-
5
- OhMyAPI is a Django-flavored web-application scaffolding framework.
6
- Built around FastAPI and TortoiseORM, it 100% async.
7
- It is blazingly fast and has batteries included.
8
-
9
- Features:
10
-
11
- - Django-like project-layout and -structure
12
- - Django-like settings.py
13
- - Django-like models via TortoiseORM
14
- - Django-like model.Meta class for model configuration
15
- - Django-like advanced permissions system
16
- - Django-like migrations (makemigrations & migrate) via Aerich
17
- - Django-like CLI for interfacing with your projects (startproject, startapp, shell, serve, etc)
18
- - various optional builtin apps
19
- - highly configurable and customizable
20
- - 100% async
21
-
22
- ## Getting started
23
-
24
- **Creating a Project**
25
-
26
- ```
27
- pip install ohmyapi
28
- ohmyapi startproject myproject
29
- cd myproject
30
- ```
31
-
32
- This will create the following directory structure:
33
-
34
- ```
35
- myproject/
36
- - pyproject.toml
37
- - settings.py
38
- ```
39
-
40
- Run your project with:
41
-
42
- ```
43
- ohmyapi serve
44
- ```
45
-
46
- In your browser go to:
47
- - http://localhost:8000/docs
48
-
49
- **Creating an App**
50
-
51
- Create a new app by:
52
-
53
- ```
54
- ohmyapi startapp myapp
55
- ```
56
-
57
- This will lead to the following directory structure:
58
-
59
- ```
60
- myproject/
61
- - myapp/
62
- - __init__.py
63
- - models.py
64
- - routes.py
65
- - pyproject.toml
66
- - settings.py
67
- ```
68
-
69
- Add 'myapp' to your `INSTALLED_APPS` in `settings.py`.
70
-
71
- Write your first model in `myapp/models.py`:
72
-
73
- ```python
74
- from ohmyapi.db import Model, field
75
-
76
-
77
- class Person(Model):
78
- id: int = field.IntField(min=1, pk=True)
79
- name: str = field.CharField(min_length=1, max_length=255)
80
- username: str = field.CharField(min_length=1, max_length=255, unique=True)
81
- age: int = field.IntField(min=0)
82
- ```
83
-
84
- Next, create your endpoints in `myapp/routes.py`:
85
-
86
- ```python
87
- from fastapi import APIRouter, HTTPException
88
- from tortoise.exceptions import DoesNotExist
89
-
90
- from .models import Person
91
-
92
- router = APIRouter(prefix="/myapp")
93
-
94
-
95
- @router.get("/")
96
- async def list():
97
- return await Person.Schema.many.from_queryset(Person.all())
98
-
99
-
100
- @router.get("/:id")
101
- async def get(id: int):
102
- try:
103
- return await Person.Schema.one(Person.get(pk=id))
104
- except DoesNotExist:
105
- raise HTTPException(status_code=404, detail="item not found")
106
-
107
- ...
108
- ```
109
-
110
- ## Migrations
111
-
112
- Before we can run the app, we need to create and initialize the database.
113
-
114
- Similar to Django, first run:
115
-
116
- ```
117
- ohmyapi makemigrations [ <app> ] # no app means all INSTALLED_APPS
118
- ```
119
-
120
- This will create a `migrations/` folder in you project root.
121
-
122
- ```
123
- myproject/
124
- - myapp/
125
- - __init__.py
126
- - models.py
127
- - routes.py
128
- - migrations/
129
- - myapp/
130
- - pyproject.toml
131
- - settings.py
132
- ```
133
-
134
- Apply your migrations via:
135
-
136
- ```
137
- ohmyapi migrate [ <app> ] # no app means all INSTALLED_APPS
138
- ```
139
-
140
- Run your project:
141
-
142
- ```
143
- ohmyapi serve
144
- ```
145
-
146
- ## Shell
147
-
148
- Similar to Django, you can attach to an interactive shell with your project already loaded inside.
149
-
150
- ```
151
- ohmyapi shell
152
- ```
153
-
154
- ## Authentication
155
-
156
- A builtin auth app is available.
157
-
158
- Simply add `ohmyapi_auth` to your INSTALLED_APPS and define a JWT_SECRET in your `settings.py`.
159
- Remember to `makemigrations` and `migrate` for the auth tables to be created in the database.
160
-
161
- `settings.py`:
162
-
163
- ```
164
- INSTALLED_APPS = [
165
- 'ohmyapi_auth',
166
- ...
167
- ]
168
-
169
- JWT_SECRET = "t0ps3cr3t"
170
- ```
171
-
172
- Create a super-user:
173
-
174
- ```
175
- ohmyapi createsuperuser
176
- ```
177
-
@@ -1,36 +0,0 @@
1
- [project]
2
- name = "ohmyapi"
3
- version = "0.1.6"
4
- description = "A Django-like but async web-framework based on FastAPI and TortoiseORM."
5
- license = "MIT"
6
- keywords = ["fastapi", "tortoise", "orm", "async", "web-framework"]
7
- authors = [
8
- {name = "Brian Wiborg", email = "me@brianwib.org"}
9
- ]
10
- readme = "README.md"
11
- requires-python = ">=3.13"
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]
28
- packages = [{include = "ohmyapi", from = "src"}]
29
-
30
- [tool.poetry.scripts]
31
- ohmyapi = "ohmyapi.cli:main"
32
-
33
- [build-system]
34
- requires = ["poetry-core>=2.0.0,<3.0.0"]
35
- build-backend = "poetry.core.masonry.api"
36
-
@@ -1,13 +0,0 @@
1
- [tool.poetry]
2
- name = "{{ project_name }}"
3
- version = "0.1.0"
4
- description = "OhMyAPI project"
5
- authors = ["You <you@example.com>"]
6
-
7
- [tool.poetry.dependencies]
8
- python = "^3.10"
9
- fastapi = "^0.115"
10
- uvicorn = "^0.30"
11
- tortoise-orm = "^0.20"
12
- aerich = "^0.7"
13
-
@@ -1,3 +0,0 @@
1
- from tortoise import fields as field
2
- from .model import Model
3
-
@@ -1 +0,0 @@
1
- from .model import Model, fields
@@ -1,2 +0,0 @@
1
- from fastapi import APIRouter, Depends
2
-
File without changes