ohmyapi 0.1.25__tar.gz → 0.1.27__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.25 → ohmyapi-0.1.27}/PKG-INFO +15 -11
  2. {ohmyapi-0.1.25 → ohmyapi-0.1.27}/README.md +9 -7
  3. {ohmyapi-0.1.25 → ohmyapi-0.1.27}/pyproject.toml +4 -4
  4. {ohmyapi-0.1.25 → ohmyapi-0.1.27}/src/ohmyapi/builtin/auth/routes.py +2 -2
  5. {ohmyapi-0.1.25 → ohmyapi-0.1.27}/src/ohmyapi/builtin/demo/routes.py +9 -9
  6. {ohmyapi-0.1.25 → ohmyapi-0.1.27}/src/ohmyapi/core/runtime.py +4 -12
  7. {ohmyapi-0.1.25 → ohmyapi-0.1.27}/src/ohmyapi/core/templates/app/routes.py.j2 +4 -4
  8. {ohmyapi-0.1.25 → ohmyapi-0.1.27}/src/ohmyapi/db/model/model.py +5 -0
  9. {ohmyapi-0.1.25 → ohmyapi-0.1.27}/src/ohmyapi/__init__.py +0 -0
  10. {ohmyapi-0.1.25 → ohmyapi-0.1.27}/src/ohmyapi/__main__.py +0 -0
  11. {ohmyapi-0.1.25 → ohmyapi-0.1.27}/src/ohmyapi/builtin/auth/__init__.py +0 -0
  12. {ohmyapi-0.1.25 → ohmyapi-0.1.27}/src/ohmyapi/builtin/auth/models.py +0 -0
  13. {ohmyapi-0.1.25 → ohmyapi-0.1.27}/src/ohmyapi/builtin/auth/permissions.py +0 -0
  14. {ohmyapi-0.1.25 → ohmyapi-0.1.27}/src/ohmyapi/builtin/demo/__init__.py +0 -0
  15. {ohmyapi-0.1.25 → ohmyapi-0.1.27}/src/ohmyapi/builtin/demo/models.py +0 -0
  16. {ohmyapi-0.1.25 → ohmyapi-0.1.27}/src/ohmyapi/cli.py +0 -0
  17. {ohmyapi-0.1.25 → ohmyapi-0.1.27}/src/ohmyapi/core/__init__.py +0 -0
  18. {ohmyapi-0.1.25 → ohmyapi-0.1.27}/src/ohmyapi/core/scaffolding.py +0 -0
  19. {ohmyapi-0.1.25 → ohmyapi-0.1.27}/src/ohmyapi/core/templates/app/__init__.py.j2 +0 -0
  20. {ohmyapi-0.1.25 → ohmyapi-0.1.27}/src/ohmyapi/core/templates/app/models.py.j2 +0 -0
  21. {ohmyapi-0.1.25 → ohmyapi-0.1.27}/src/ohmyapi/core/templates/project/README.md.j2 +0 -0
  22. {ohmyapi-0.1.25 → ohmyapi-0.1.27}/src/ohmyapi/core/templates/project/pyproject.toml.j2 +0 -0
  23. {ohmyapi-0.1.25 → ohmyapi-0.1.27}/src/ohmyapi/core/templates/project/settings.py.j2 +0 -0
  24. {ohmyapi-0.1.25 → ohmyapi-0.1.27}/src/ohmyapi/db/__init__.py +0 -0
  25. {ohmyapi-0.1.25 → ohmyapi-0.1.27}/src/ohmyapi/db/exceptions.py +0 -0
  26. {ohmyapi-0.1.25 → ohmyapi-0.1.27}/src/ohmyapi/db/model/__init__.py +0 -0
  27. {ohmyapi-0.1.25 → ohmyapi-0.1.27}/src/ohmyapi/router.py +0 -0
@@ -1,13 +1,15 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ohmyapi
3
- Version: 0.1.25
4
- Summary: A Django-like but async web-framework based on FastAPI and TortoiseORM.
3
+ Version: 0.1.27
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.11
10
10
  Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.11
12
+ Classifier: Programming Language :: Python :: 3.12
11
13
  Classifier: Programming Language :: Python :: 3.13
12
14
  Classifier: Programming Language :: Python :: 3.14
13
15
  Provides-Extra: auth
@@ -157,6 +159,8 @@ Next, create your endpoints in `tournament/routes.py`:
157
159
  from ohmyapi.router import APIRouter, HTTPException, HTTPStatus
158
160
  from ohmyapi.db.exceptions import DoesNotExist
159
161
 
162
+ from typing import List
163
+
160
164
  from .models import Tournament
161
165
 
162
166
  # OhMyAPI will automatically pick up all instances of `fastapi.APIRouter` and
@@ -169,23 +173,23 @@ from .models import Tournament
169
173
  tournament_router = APIRouter(prefix="/tournament", tags=['Tournament'])
170
174
 
171
175
 
172
- @tournament_router.get("/")
176
+ @tournament_router.get("/", response_model=List[Tournament.Schema()])
173
177
  async def list():
174
178
  queryset = Tournament.all()
175
179
  return await Tournament.Schema.model.from_queryset(queryset)
176
180
 
177
181
 
178
182
  @tournament_router.post("/", status_code=HTTPStatus.CREATED)
179
- async def post(tournament: Tournament.Schema.readonly):
183
+ async def post(tournament: Tournament.Schema(readonly=True)):
180
184
  queryset = Tournament.create(**payload.model_dump())
181
- return await Tournament.Schema.model.from_queryset(queryset)
185
+ return await Tournament.Schema().from_queryset(queryset)
182
186
 
183
187
 
184
- @tournament_router.get("/:id")
188
+ @tournament_router.get("/:id", response_model=Tournament.Schema())
185
189
  async def get(id: str):
186
190
  try:
187
191
  queryset = Tournament.get(id=id)
188
- return await Tournament.Schema.model.from_queryset_single(tournament)
192
+ return await Tournament.Schema().from_queryset_single(tournament)
189
193
  except DoesNotExist:
190
194
  raise HTTPException(status_code=404, detail="not found")
191
195
 
@@ -305,7 +309,7 @@ router = APIRouter(prefix="/tournament", tags=["Tournament"])
305
309
  @router.get("/")
306
310
  async def list(user: auth.User = Depends(permissions.require_authenticated)):
307
311
  queryset = Tournament.all()
308
- return await Tournament.Schema.model.from_queryset(queryset)
312
+ return await Tournament.Schema().from_queryset(queryset)
309
313
 
310
314
 
311
315
  ...
@@ -347,7 +351,7 @@ router = APIRouter(prefix="/tournament", tags=["Tournament"])
347
351
  @router.get("/teams")
348
352
  async def teams(user: auth.User = Depends(permissions.require_authenticated)):
349
353
  queryset = Team.for_user(user)
350
- return await Tournament.Schema.model.from_queryset(queryset)
354
+ return await Tournament.Schema().from_queryset(queryset)
351
355
  ```
352
356
 
353
357
  ## Shell
@@ -125,6 +125,8 @@ 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
132
  # OhMyAPI will automatically pick up all instances of `fastapi.APIRouter` and
@@ -137,23 +139,23 @@ from .models import Tournament
137
139
  tournament_router = APIRouter(prefix="/tournament", tags=['Tournament'])
138
140
 
139
141
 
140
- @tournament_router.get("/")
142
+ @tournament_router.get("/", response_model=List[Tournament.Schema()])
141
143
  async def list():
142
144
  queryset = Tournament.all()
143
145
  return await Tournament.Schema.model.from_queryset(queryset)
144
146
 
145
147
 
146
148
  @tournament_router.post("/", status_code=HTTPStatus.CREATED)
147
- async def post(tournament: Tournament.Schema.readonly):
149
+ async def post(tournament: Tournament.Schema(readonly=True)):
148
150
  queryset = Tournament.create(**payload.model_dump())
149
- return await Tournament.Schema.model.from_queryset(queryset)
151
+ return await Tournament.Schema().from_queryset(queryset)
150
152
 
151
153
 
152
- @tournament_router.get("/:id")
154
+ @tournament_router.get("/:id", response_model=Tournament.Schema())
153
155
  async def get(id: str):
154
156
  try:
155
157
  queryset = Tournament.get(id=id)
156
- return await Tournament.Schema.model.from_queryset_single(tournament)
158
+ return await Tournament.Schema().from_queryset_single(tournament)
157
159
  except DoesNotExist:
158
160
  raise HTTPException(status_code=404, detail="not found")
159
161
 
@@ -273,7 +275,7 @@ router = APIRouter(prefix="/tournament", tags=["Tournament"])
273
275
  @router.get("/")
274
276
  async def list(user: auth.User = Depends(permissions.require_authenticated)):
275
277
  queryset = Tournament.all()
276
- return await Tournament.Schema.model.from_queryset(queryset)
278
+ return await Tournament.Schema().from_queryset(queryset)
277
279
 
278
280
 
279
281
  ...
@@ -315,7 +317,7 @@ router = APIRouter(prefix="/tournament", tags=["Tournament"])
315
317
  @router.get("/teams")
316
318
  async def teams(user: auth.User = Depends(permissions.require_authenticated)):
317
319
  queryset = Team.for_user(user)
318
- return await Tournament.Schema.model.from_queryset(queryset)
320
+ return await Tournament.Schema().from_queryset(queryset)
319
321
  ```
320
322
 
321
323
  ## Shell
@@ -1,14 +1,14 @@
1
1
  [project]
2
2
  name = "ohmyapi"
3
- version = "0.1.25"
4
- description = "A Django-like but async web-framework based on FastAPI and TortoiseORM."
3
+ version = "0.1.27"
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.11"
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)
@@ -119,26 +119,18 @@ class Project:
119
119
  def build_aerich_command(
120
120
  self, app_label: str, db_url: Optional[str] = None
121
121
  ) -> AerichCommand:
122
- # Resolve label to flat_label
123
- if app_label in self._apps:
124
- flat_label = app_label
125
- else:
126
- candidate = app_label.replace(".", "_")
127
- if candidate in self._apps:
128
- flat_label = candidate
129
- else:
130
- raise RuntimeError(f"App '{app_label}' is not registered")
122
+ if app_label not in self._apps:
123
+ raise RuntimeError(f"App '{app_label}' is not registered")
131
124
 
132
125
  # Get a fresh copy of the config (without aerich.models anywhere)
133
126
  tortoise_cfg = copy.deepcopy(self.build_tortoise_config(db_url=db_url))
134
127
 
135
128
  # Append aerich.models to the models list of the target app only
136
- if flat_label in tortoise_cfg["apps"]:
137
- tortoise_cfg["apps"][flat_label]["models"].append("aerich.models")
129
+ tortoise_cfg["apps"][app_label]["models"].append("aerich.models")
138
130
 
139
131
  return AerichCommand(
140
132
  tortoise_config=tortoise_cfg,
141
- app=flat_label,
133
+ app=app_label,
142
134
  location=str(self.migrations_dir),
143
135
  )
144
136
 
@@ -14,16 +14,16 @@ from typing import List
14
14
  router = APIRouter(prefix="/{{ app_name }}", tags=['{{ app_name }}'])
15
15
 
16
16
 
17
- @router.get("/")
17
+ @router.get("/", response_model=List)
18
18
  async def list():
19
19
  """List all ..."""
20
20
  return []
21
21
 
22
22
 
23
- @router.post("/")
23
+ @router.post("/", status_code=HTTPStatus.CREATED)
24
24
  async def post():
25
25
  """Create ..."""
26
- return HTTPException(status_code=HTTPStatus.CREATED)
26
+ raise HTTPException(status_code=HTTPStatus.IM_A_TEAPOT)
27
27
 
28
28
 
29
29
  @router.get("/{id}")
@@ -38,7 +38,7 @@ async def put(id: str):
38
38
  return HTTPException(status_code=HTTPStatus.ACCEPTED)
39
39
 
40
40
 
41
- @router.delete("/{id}")
41
+ @router.delete("/{id}", status_code=HTTPStatus.ACCEPTED)
42
42
  async def delete(id: str):
43
43
  return HTTPException(status_code=HTTPStatus.ACCEPTED)
44
44
 
@@ -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