ohmyapi 0.1.24__tar.gz → 0.1.26__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 (27) hide show
  1. {ohmyapi-0.1.24 → ohmyapi-0.1.26}/PKG-INFO +36 -16
  2. {ohmyapi-0.1.24 → ohmyapi-0.1.26}/README.md +29 -12
  3. {ohmyapi-0.1.24 → ohmyapi-0.1.26}/pyproject.toml +4 -4
  4. {ohmyapi-0.1.24 → ohmyapi-0.1.26}/src/ohmyapi/builtin/auth/routes.py +2 -2
  5. {ohmyapi-0.1.24 → ohmyapi-0.1.26}/src/ohmyapi/builtin/demo/routes.py +9 -9
  6. {ohmyapi-0.1.24 → ohmyapi-0.1.26}/src/ohmyapi/core/runtime.py +7 -4
  7. {ohmyapi-0.1.24 → ohmyapi-0.1.26}/src/ohmyapi/core/templates/app/routes.py.j2 +10 -7
  8. {ohmyapi-0.1.24 → ohmyapi-0.1.26}/src/ohmyapi/core/templates/project/settings.py.j2 +1 -1
  9. {ohmyapi-0.1.24 → ohmyapi-0.1.26}/src/ohmyapi/db/model/model.py +5 -0
  10. {ohmyapi-0.1.24 → ohmyapi-0.1.26}/src/ohmyapi/__init__.py +0 -0
  11. {ohmyapi-0.1.24 → ohmyapi-0.1.26}/src/ohmyapi/__main__.py +0 -0
  12. {ohmyapi-0.1.24 → ohmyapi-0.1.26}/src/ohmyapi/builtin/auth/__init__.py +0 -0
  13. {ohmyapi-0.1.24 → ohmyapi-0.1.26}/src/ohmyapi/builtin/auth/models.py +0 -0
  14. {ohmyapi-0.1.24 → ohmyapi-0.1.26}/src/ohmyapi/builtin/auth/permissions.py +0 -0
  15. {ohmyapi-0.1.24 → ohmyapi-0.1.26}/src/ohmyapi/builtin/demo/__init__.py +0 -0
  16. {ohmyapi-0.1.24 → ohmyapi-0.1.26}/src/ohmyapi/builtin/demo/models.py +0 -0
  17. {ohmyapi-0.1.24 → ohmyapi-0.1.26}/src/ohmyapi/cli.py +0 -0
  18. {ohmyapi-0.1.24 → ohmyapi-0.1.26}/src/ohmyapi/core/__init__.py +0 -0
  19. {ohmyapi-0.1.24 → ohmyapi-0.1.26}/src/ohmyapi/core/scaffolding.py +0 -0
  20. {ohmyapi-0.1.24 → ohmyapi-0.1.26}/src/ohmyapi/core/templates/app/__init__.py.j2 +0 -0
  21. {ohmyapi-0.1.24 → ohmyapi-0.1.26}/src/ohmyapi/core/templates/app/models.py.j2 +0 -0
  22. {ohmyapi-0.1.24 → ohmyapi-0.1.26}/src/ohmyapi/core/templates/project/README.md.j2 +0 -0
  23. {ohmyapi-0.1.24 → ohmyapi-0.1.26}/src/ohmyapi/core/templates/project/pyproject.toml.j2 +0 -0
  24. {ohmyapi-0.1.24 → ohmyapi-0.1.26}/src/ohmyapi/db/__init__.py +0 -0
  25. {ohmyapi-0.1.24 → ohmyapi-0.1.26}/src/ohmyapi/db/exceptions.py +0 -0
  26. {ohmyapi-0.1.24 → ohmyapi-0.1.26}/src/ohmyapi/db/model/__init__.py +0 -0
  27. {ohmyapi-0.1.24 → ohmyapi-0.1.26}/src/ohmyapi/router.py +0 -0
@@ -1,13 +1,16 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ohmyapi
3
- Version: 0.1.24
4
- Summary: A Django-like but async web-framework based on FastAPI and TortoiseORM.
3
+ Version: 0.1.26
4
+ Summary: Django-flavored scaffolding and management layer around FastAPI, Pydantic, TortoiseORM and Aerich migrations
5
5
  License-Expression: MIT
6
- Keywords: fastapi,tortoise,orm,async,web-framework
6
+ Keywords: fastapi,tortoise,orm,pydantic,async,web-framework
7
7
  Author: Brian Wiborg
8
8
  Author-email: me@brianwib.org
9
- Requires-Python: >=3.13
9
+ Requires-Python: >=3.10
10
10
  Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.10
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
11
14
  Classifier: Programming Language :: Python :: 3.13
12
15
  Classifier: Programming Language :: Python :: 3.14
13
16
  Provides-Extra: auth
@@ -62,7 +65,7 @@ It is ***blazingly fast***, extremely ***fun to use*** and comes with ***batteri
62
65
  **Creating a Project**
63
66
 
64
67
  ```
65
- pip install ohmyapi
68
+ pipx install ohmyapi
66
69
  ohmyapi startproject myproject
67
70
  cd myproject
68
71
  ```
@@ -157,33 +160,50 @@ Next, create your endpoints in `tournament/routes.py`:
157
160
  from ohmyapi.router import APIRouter, HTTPException, HTTPStatus
158
161
  from ohmyapi.db.exceptions import DoesNotExist
159
162
 
163
+ from typing import List
164
+
160
165
  from .models import Tournament
161
166
 
162
- # Expose your app's routes via `router = fastapi.APIRouter`.
163
- # Use prefixes wisely to avoid cross-app namespace-collisions.
167
+ # OhMyAPI will automatically pick up all instances of `fastapi.APIRouter` and
168
+ # add their routes to the main project router.
169
+ #
170
+ # Note:
171
+ # Use prefixes wisely to avoid cross-app namespace-collisions!
164
172
  # Tags improve the UX of the OpenAPI docs at /docs.
165
- router = APIRouter(prefix="/tournament", tags=['Tournament'])
173
+ #
174
+ tournament_router = APIRouter(prefix="/tournament", tags=['Tournament'])
166
175
 
167
- @router.get("/")
176
+
177
+ @tournament_router.get("/", response_model=List[Tournament.Schema()])
168
178
  async def list():
169
179
  queryset = Tournament.all()
170
180
  return await Tournament.Schema.model.from_queryset(queryset)
171
181
 
172
182
 
173
- @router.post("/", status_code=HTTPStatus.CREATED)
174
- async def post(tournament: Tournament.Schema.readonly):
183
+ @tournament_router.post("/", status_code=HTTPStatus.CREATED)
184
+ async def post(tournament: Tournament.Schema(readonly=True)):
175
185
  queryset = Tournament.create(**payload.model_dump())
176
- return await Tournament.Schema.model.from_queryset(queryset)
186
+ return await Tournament.Schema().from_queryset(queryset)
177
187
 
178
188
 
179
- @router.get("/:id")
189
+ @tournament_router.get("/:id", response_model=Tournament.Schema())
180
190
  async def get(id: str):
181
191
  try:
182
192
  queryset = Tournament.get(id=id)
183
- return await Tournament.Schema.model.from_queryset_single(tournament)
193
+ return await Tournament.Schema().from_queryset_single(tournament)
194
+ except DoesNotExist:
195
+ raise HTTPException(status_code=404, detail="not found")
196
+
197
+
198
+ @tournament_router.delete("/:id")
199
+ async def delete(id: str):
200
+ try:
201
+ tournament = await Tournament.get(id=id)
202
+ return await Tournament.Schema.model.from_queryset(tournament.delete())
184
203
  except DoesNotExist:
185
204
  raise HTTPException(status_code=404, detail="not found")
186
205
 
206
+
187
207
  ...
188
208
  ```
189
209
 
@@ -290,7 +310,7 @@ router = APIRouter(prefix="/tournament", tags=["Tournament"])
290
310
  @router.get("/")
291
311
  async def list(user: auth.User = Depends(permissions.require_authenticated)):
292
312
  queryset = Tournament.all()
293
- return await Tournament.Schema.model.from_queryset(queryset)
313
+ return await Tournament.Schema().from_queryset(queryset)
294
314
 
295
315
 
296
316
  ...
@@ -332,7 +352,7 @@ router = APIRouter(prefix="/tournament", tags=["Tournament"])
332
352
  @router.get("/teams")
333
353
  async def teams(user: auth.User = Depends(permissions.require_authenticated)):
334
354
  queryset = Team.for_user(user)
335
- return await Tournament.Schema.model.from_queryset(queryset)
355
+ return await Tournament.Schema().from_queryset(queryset)
336
356
  ```
337
357
 
338
358
  ## Shell
@@ -30,7 +30,7 @@ It is ***blazingly fast***, extremely ***fun to use*** and comes with ***batteri
30
30
  **Creating a Project**
31
31
 
32
32
  ```
33
- pip install ohmyapi
33
+ pipx install ohmyapi
34
34
  ohmyapi startproject myproject
35
35
  cd myproject
36
36
  ```
@@ -125,33 +125,50 @@ Next, create your endpoints in `tournament/routes.py`:
125
125
  from ohmyapi.router import APIRouter, HTTPException, HTTPStatus
126
126
  from ohmyapi.db.exceptions import DoesNotExist
127
127
 
128
+ from typing import List
129
+
128
130
  from .models import Tournament
129
131
 
130
- # Expose your app's routes via `router = fastapi.APIRouter`.
131
- # Use prefixes wisely to avoid cross-app namespace-collisions.
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!
132
137
  # Tags improve the UX of the OpenAPI docs at /docs.
133
- router = APIRouter(prefix="/tournament", tags=['Tournament'])
138
+ #
139
+ tournament_router = APIRouter(prefix="/tournament", tags=['Tournament'])
134
140
 
135
- @router.get("/")
141
+
142
+ @tournament_router.get("/", response_model=List[Tournament.Schema()])
136
143
  async def list():
137
144
  queryset = Tournament.all()
138
145
  return await Tournament.Schema.model.from_queryset(queryset)
139
146
 
140
147
 
141
- @router.post("/", status_code=HTTPStatus.CREATED)
142
- async def post(tournament: Tournament.Schema.readonly):
148
+ @tournament_router.post("/", status_code=HTTPStatus.CREATED)
149
+ async def post(tournament: Tournament.Schema(readonly=True)):
143
150
  queryset = Tournament.create(**payload.model_dump())
144
- return await Tournament.Schema.model.from_queryset(queryset)
151
+ return await Tournament.Schema().from_queryset(queryset)
145
152
 
146
153
 
147
- @router.get("/:id")
154
+ @tournament_router.get("/:id", response_model=Tournament.Schema())
148
155
  async def get(id: str):
149
156
  try:
150
157
  queryset = Tournament.get(id=id)
151
- return await Tournament.Schema.model.from_queryset_single(tournament)
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())
152
168
  except DoesNotExist:
153
169
  raise HTTPException(status_code=404, detail="not found")
154
170
 
171
+
155
172
  ...
156
173
  ```
157
174
 
@@ -258,7 +275,7 @@ router = APIRouter(prefix="/tournament", tags=["Tournament"])
258
275
  @router.get("/")
259
276
  async def list(user: auth.User = Depends(permissions.require_authenticated)):
260
277
  queryset = Tournament.all()
261
- return await Tournament.Schema.model.from_queryset(queryset)
278
+ return await Tournament.Schema().from_queryset(queryset)
262
279
 
263
280
 
264
281
  ...
@@ -300,7 +317,7 @@ router = APIRouter(prefix="/tournament", tags=["Tournament"])
300
317
  @router.get("/teams")
301
318
  async def teams(user: auth.User = Depends(permissions.require_authenticated)):
302
319
  queryset = Team.for_user(user)
303
- return await Tournament.Schema.model.from_queryset(queryset)
320
+ return await Tournament.Schema().from_queryset(queryset)
304
321
  ```
305
322
 
306
323
  ## Shell
@@ -1,14 +1,14 @@
1
1
  [project]
2
2
  name = "ohmyapi"
3
- version = "0.1.24"
4
- description = "A Django-like but async web-framework based on FastAPI and TortoiseORM."
3
+ version = "0.1.26"
4
+ description = "Django-flavored scaffolding and management layer around FastAPI, Pydantic, TortoiseORM and Aerich migrations"
5
5
  license = "MIT"
6
- keywords = ["fastapi", "tortoise", "orm", "async", "web-framework"]
6
+ keywords = ["fastapi", "tortoise", "orm", "pydantic", "async", "web-framework"]
7
7
  authors = [
8
8
  {name = "Brian Wiborg", email = "me@brianwib.org"}
9
9
  ]
10
10
  readme = "README.md"
11
- requires-python = ">=3.13"
11
+ requires-python = ">=3.10"
12
12
 
13
13
  dependencies = [
14
14
  "typer >=0.19.1,<0.20.0",
@@ -207,7 +207,7 @@ async def introspect(token: Dict = Depends(get_token)):
207
207
  return token
208
208
 
209
209
 
210
- @router.get("/me", response_model=User.Schema.model)
210
+ @router.get("/me", response_model=User.Schema())
211
211
  async def me(user: User = Depends(get_current_user)):
212
212
  """Return the currently authenticated user."""
213
- return await User.Schema.model.from_tortoise_orm(user)
213
+ return await User.Schema().from_tortoise_orm(user)
@@ -8,29 +8,29 @@ from typing import List
8
8
  # Expose your app's routes via `router = fastapi.APIRouter`.
9
9
  # Use prefixes wisely to avoid cross-app namespace-collisions.
10
10
  # Tags improve the UX of the OpenAPI docs at /docs.
11
- router = APIRouter(prefix="/tournemant")
11
+ router = APIRouter(prefix="/tournament")
12
12
 
13
13
 
14
14
  @router.get(
15
- "/", tags=["tournament"], response_model=List[models.Tournament.Schema.model]
15
+ "/", tags=["tournament"], response_model=List[models.Tournament.Schema()]
16
16
  )
17
17
  async def list():
18
18
  """List all tournaments."""
19
- return await models.Tournament.Schema.model.from_queryset(models.Tournament.all())
19
+ return await models.Tournament.Schema().from_queryset(models.Tournament.all())
20
20
 
21
21
 
22
22
  @router.post("/", tags=["tournament"], status_code=HTTPStatus.CREATED)
23
- async def post(tournament: models.Tournament.Schema.readonly):
23
+ async def post(tournament: models.Tournament.Schema(readonly=True)):
24
24
  """Create tournament."""
25
- return await models.Tournament.Schema.model.from_queryset(
25
+ return await models.Tournament.Schema().from_queryset(
26
26
  models.Tournament.create(**tournament.model_dump())
27
27
  )
28
28
 
29
29
 
30
- @router.get("/{id}", tags=["tournament"], response_model=models.Tournament.Schema.model)
30
+ @router.get("/{id}", tags=["tournament"], response_model=models.Tournament.Schema())
31
31
  async def get(id: str):
32
32
  """Get tournament by id."""
33
- return await models.Tournament.Schema.model.from_queryset(
33
+ return await models.Tournament.Schema().from_queryset(
34
34
  models.Tournament.get(id=id)
35
35
  )
36
36
 
@@ -43,12 +43,12 @@ async def get(id: str):
43
43
  )
44
44
  async def put(tournament: models.Tournament.Schema.model):
45
45
  """Update tournament."""
46
- return await models.Tournament.Schema.model.from_queryset(
46
+ return await models.Tournament.Schema().from_queryset(
47
47
  models.Tournament.update(**tournament.model_dump())
48
48
  )
49
49
 
50
50
 
51
- @router.delete("/{id}", tags=["tournament"])
51
+ @router.delete("/{id}", status_code=HTTPStatus.ACCEPTED, tags=["tournament"])
52
52
  async def delete(id: str):
53
53
  try:
54
54
  tournament = await models.Tournament.get(id=id)
@@ -211,7 +211,7 @@ class App:
211
211
  self.model_modules: List[str] = []
212
212
 
213
213
  # The APIRouter
214
- self.router: Optional[APIRouter] = None
214
+ self.router: APIRouter = APIRouter()
215
215
 
216
216
  # Import the app, so its __init__.py runs.
217
217
  importlib.import_module(self.name)
@@ -226,9 +226,12 @@ class App:
226
226
  # Locate the APIRouter
227
227
  try:
228
228
  routes_mod = importlib.import_module(f"{self.name}.routes")
229
- router = getattr(routes_mod, "router", None)
230
- if isinstance(router, APIRouter):
231
- self.router = router
229
+ for attr_name in dir(routes_mod):
230
+ if attr_name.startswith("__"):
231
+ continue
232
+ attr = getattr(routes_mod, attr_name)
233
+ if isinstance(attr, APIRouter):
234
+ self.router.include_router(attr)
232
235
  except ModuleNotFoundError:
233
236
  pass
234
237
 
@@ -4,23 +4,26 @@ from . import models
4
4
 
5
5
  from typing import List
6
6
 
7
- # Expose your app's routes via `router = fastapi.APIRouter`.
8
- # Use prefixes wisely to avoid cross-app namespace-collisions.
7
+ # OhMyAPI will automatically pick up all instances of `fastapi.APIRouter` and
8
+ # add their routes to the main project router.
9
+ #
10
+ # Note:
11
+ # Use prefixes wisely to avoid cross-app namespace-collisions!
9
12
  # Tags improve the UX of the OpenAPI docs at /docs.
13
+ #
10
14
  router = APIRouter(prefix="/{{ app_name }}", tags=['{{ app_name }}'])
11
15
 
12
16
 
13
-
14
- @router.get("/")
17
+ @router.get("/", response_model=List)
15
18
  async def list():
16
19
  """List all ..."""
17
20
  return []
18
21
 
19
22
 
20
- @router.post("/")
23
+ @router.post("/", status_code=HTTPStatus.CREATED)
21
24
  async def post():
22
25
  """Create ..."""
23
- return HTTPException(status_code=HTTPStatus.CREATED)
26
+ raise HTTPException(status_code=HTTPStatus.IM_A_TEAPOT)
24
27
 
25
28
 
26
29
  @router.get("/{id}")
@@ -35,7 +38,7 @@ async def put(id: str):
35
38
  return HTTPException(status_code=HTTPStatus.ACCEPTED)
36
39
 
37
40
 
38
- @router.delete("/{id}")
41
+ @router.delete("/{id}", status_code=HTTPStatus.ACCEPTED)
39
42
  async def delete(id: str):
40
43
  return HTTPException(status_code=HTTPStatus.ACCEPTED)
41
44
 
@@ -2,6 +2,6 @@
2
2
  PROJECT_NAME = "MyProject"
3
3
  DATABASE_URL = "sqlite://db.sqlite3"
4
4
  INSTALLED_APPS = [
5
- #'ohmyapi_auth',
5
+ #"ohmyapi_auth",
6
6
  ]
7
7
 
@@ -36,6 +36,11 @@ class ModelMeta(type(TortoiseModel)):
36
36
  schema_opts = getattr(new_cls, "Schema", None)
37
37
 
38
38
  class BoundSchema:
39
+ def __call__(self, readonly: bool = False):
40
+ if readonly:
41
+ return self.readonly
42
+ return self.model
43
+
39
44
  @property
40
45
  def model(self):
41
46
  """Return a Pydantic model class for serializing results."""
File without changes
File without changes