ohmyapi 0.1.22__py3-none-any.whl → 0.1.24__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.
@@ -26,9 +26,62 @@ REFRESH_TOKEN_EXPIRE_SECONDS = getattr(
26
26
  oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/auth/login")
27
27
 
28
28
 
29
- def create_token(data: dict, expires_in: int) -> str:
30
- to_encode = data.copy()
31
- to_encode.update({"exp": int(time.time()) + expires_in})
29
+ class ClaimsUser(BaseModel):
30
+ username: str
31
+ email: str
32
+ is_admin: bool
33
+ is_staff: bool
34
+
35
+
36
+ class Claims(BaseModel):
37
+ type: str
38
+ sub: str
39
+ user: ClaimsUser
40
+ roles: List[str]
41
+ exp: str
42
+
43
+
44
+ class AccessToken(BaseModel):
45
+ token_type: str
46
+ access_token: str
47
+
48
+
49
+ class RefreshToken(AccessToken):
50
+ refresh_token: str
51
+
52
+
53
+ class LoginRequest(BaseModel):
54
+ username: str
55
+ password: str
56
+
57
+
58
+ class TokenType(str, Enum):
59
+ """
60
+ Helper for indicating the token type when generating claims.
61
+ """
62
+
63
+ access = "access"
64
+ refresh = "refresh"
65
+
66
+
67
+ def claims(token_type: TokenType, user: User, groups: List[Group] = []) -> Claims:
68
+ return Claims(
69
+ type=token_type,
70
+ sub=str(user.id),
71
+ user=ClaimsUser(
72
+ username=user.username,
73
+ email=user.email,
74
+ is_admin=user.is_admin,
75
+ is_staff=user.is_staff,
76
+ ),
77
+ roles=[g.name for g in groups],
78
+ exp="",
79
+ )
80
+
81
+
82
+ def create_token(claims: Claims, expires_in: int) -> str:
83
+ to_encode = claims.model_dump()
84
+ to_encode["exp"] = int(time.time()) + expires_in
32
85
  token = jwt.encode(to_encode, JWT_SECRET, algorithm=JWT_ALGORITHM)
33
86
  if isinstance(token, bytes):
34
87
  token = token.decode("utf-8")
@@ -48,29 +101,6 @@ def decode_token(token: str) -> Dict:
48
101
  )
49
102
 
50
103
 
51
- class TokenType(str, Enum):
52
- """
53
- Helper for indicating the token type when generating claims.
54
- """
55
-
56
- access = "access"
57
- refresh = "refresh"
58
-
59
-
60
- def claims(
61
- token_type: TokenType, user: User, groups: List[Group] = []
62
- ) -> Dict[str, Any]:
63
- return {
64
- "type": token_type,
65
- "sub": str(user.id),
66
- "user": {
67
- "username": user.username,
68
- "email": user.email,
69
- },
70
- "roles": [g.name for g in groups],
71
- }
72
-
73
-
74
104
  async def get_token(token: str = Depends(oauth2_scheme)) -> Dict:
75
105
  """Dependency: token introspection"""
76
106
  payload = decode_token(token)
@@ -127,12 +157,7 @@ async def require_group(
127
157
  return current_user
128
158
 
129
159
 
130
- class LoginRequest(BaseModel):
131
- username: str
132
- password: str
133
-
134
-
135
- @router.post("/login")
160
+ @router.post("/login", response_model=RefreshToken)
136
161
  async def login(form_data: LoginRequest = Body(...)):
137
162
  """Login with username & password, returns access and refresh tokens."""
138
163
  user = await User.authenticate(form_data.username, form_data.password)
@@ -148,14 +173,14 @@ async def login(form_data: LoginRequest = Body(...)):
148
173
  claims(TokenType.refresh, user), REFRESH_TOKEN_EXPIRE_SECONDS
149
174
  )
150
175
 
151
- return {
152
- "access_token": access_token,
153
- "refresh_token": refresh_token,
154
- "token_type": "bearer",
155
- }
176
+ return RefreshToken(
177
+ token_type="bearer",
178
+ access_token=access_token,
179
+ refresh_token=refresh_token,
180
+ )
156
181
 
157
182
 
158
- @router.post("/refresh")
183
+ @router.post("/refresh", response_model=AccessToken)
159
184
  async def refresh_token(refresh_token: str):
160
185
  """Exchange refresh token for new access token."""
161
186
  payload = decode_token(refresh_token)
@@ -174,15 +199,15 @@ async def refresh_token(refresh_token: str):
174
199
  new_access = create_token(
175
200
  claims(TokenType.access, user), ACCESS_TOKEN_EXPIRE_SECONDS
176
201
  )
177
- return {"access_token": new_access, "token_type": "bearer"}
202
+ return AccessToken(token_type="bearer", access_token=access_token)
178
203
 
179
204
 
180
- @router.get("/introspect")
205
+ @router.get("/introspect", response_model=Dict[str, Any])
181
206
  async def introspect(token: Dict = Depends(get_token)):
182
207
  return token
183
208
 
184
209
 
185
- @router.get("/me")
210
+ @router.get("/me", response_model=User.Schema.model)
186
211
  async def me(user: User = Depends(get_current_user)):
187
212
  """Return the currently authenticated user."""
188
- return User.Schema.one.from_orm(user)
213
+ return await User.Schema.model.from_tortoise_orm(user)
@@ -0,0 +1,2 @@
1
+ from . import models
2
+ from . import routes
@@ -0,0 +1,50 @@
1
+ from ohmyapi.db import Model, field
2
+ from ohmyapi_auth.models import User
3
+
4
+ from datetime import datetime
5
+ from decimal import Decimal
6
+ from uuid import UUID
7
+
8
+
9
+ class Team(Model):
10
+ id: UUID = field.data.UUIDField(primary_key=True)
11
+ name: str = field.TextField()
12
+ members: field.ManyToManyRelation[User] = field.ManyToManyField(
13
+ "ohmyapi_auth.User",
14
+ related_name="tournament_teams",
15
+ through="user_tournament_teams",
16
+ )
17
+
18
+ def __str__(self):
19
+ return self.name
20
+
21
+
22
+ class Tournament(Model):
23
+ id: UUID = field.data.UUIDField(primary_key=True)
24
+ name: str = field.TextField()
25
+ created: datetime = field.DatetimeField(auto_now_add=True)
26
+
27
+ def __str__(self):
28
+ return self.name
29
+
30
+
31
+ class Event(Model):
32
+ id: UUID = field.data.UUIDField(primary_key=True)
33
+ name: str = field.TextField()
34
+ tournament: field.ForeignKeyRelation[Tournament] = field.ForeignKeyField(
35
+ "ohmyapi_demo.Tournament",
36
+ related_name="events",
37
+ )
38
+ participants: field.ManyToManyRelation[Team] = field.ManyToManyField(
39
+ "ohmyapi_demo.Team",
40
+ related_name="events",
41
+ through="event_team",
42
+ )
43
+ modified: datetime = field.DatetimeField(auto_now=True)
44
+ prize: Decimal = field.DecimalField(max_digits=10, decimal_places=2, null=True)
45
+
46
+ class Schema:
47
+ exclude = ["tournament_id"]
48
+
49
+ def __str__(self):
50
+ return self.name
@@ -0,0 +1,57 @@
1
+ from ohmyapi.router import APIRouter, HTTPException, HTTPStatus
2
+ from ohmyapi.db.exceptions import DoesNotExist
3
+
4
+ from . import models
5
+
6
+ from typing import List
7
+
8
+ # Expose your app's routes via `router = fastapi.APIRouter`.
9
+ # Use prefixes wisely to avoid cross-app namespace-collisions.
10
+ # Tags improve the UX of the OpenAPI docs at /docs.
11
+ router = APIRouter(prefix="/tournemant")
12
+
13
+
14
+ @router.get(
15
+ "/", tags=["tournament"], response_model=List[models.Tournament.Schema.model]
16
+ )
17
+ async def list():
18
+ """List all tournaments."""
19
+ return await models.Tournament.Schema.model.from_queryset(models.Tournament.all())
20
+
21
+
22
+ @router.post("/", tags=["tournament"], status_code=HTTPStatus.CREATED)
23
+ async def post(tournament: models.Tournament.Schema.readonly):
24
+ """Create tournament."""
25
+ return await models.Tournament.Schema.model.from_queryset(
26
+ models.Tournament.create(**tournament.model_dump())
27
+ )
28
+
29
+
30
+ @router.get("/{id}", tags=["tournament"], response_model=models.Tournament.Schema.model)
31
+ async def get(id: str):
32
+ """Get tournament by id."""
33
+ return await models.Tournament.Schema.model.from_queryset(
34
+ models.Tournament.get(id=id)
35
+ )
36
+
37
+
38
+ @router.put(
39
+ "/{id}",
40
+ tags=["tournament"],
41
+ response_model=models.Tournament.Schema.model,
42
+ status_code=HTTPStatus.ACCEPTED,
43
+ )
44
+ async def put(tournament: models.Tournament.Schema.model):
45
+ """Update tournament."""
46
+ return await models.Tournament.Schema.model.from_queryset(
47
+ models.Tournament.update(**tournament.model_dump())
48
+ )
49
+
50
+
51
+ @router.delete("/{id}", tags=["tournament"])
52
+ async def delete(id: str):
53
+ try:
54
+ tournament = await models.Tournament.get(id=id)
55
+ return await tournament.delete()
56
+ except DoesNotExist:
57
+ raise HTTPException(status_code=HTTPStatus.NOT_FOUND, detail="not found")
@@ -10,18 +10,7 @@ readme = "README.md"
10
10
  license = { text = "MIT" }
11
11
 
12
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",
13
+ "ohmyapi (>=0.1.0,<0.2.0)"
25
14
  ]
26
15
 
27
16
  [tool.poetry.group.dev.dependencies]
ohmyapi/router.py CHANGED
@@ -1,3 +1,2 @@
1
1
  from fastapi import APIRouter, Depends, HTTPException
2
2
  from http import HTTPStatus
3
-
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ohmyapi
3
- Version: 0.1.22
3
+ Version: 0.1.24
4
4
  Summary: A Django-like but async web-framework based on FastAPI and TortoiseORM.
5
5
  License-Expression: MIT
6
6
  Keywords: fastapi,tortoise,orm,async,web-framework
@@ -34,29 +34,26 @@ Description-Content-Type: text/markdown
34
34
 
35
35
  > Think: Django RestFramework, but less clunky and 100% async.
36
36
 
37
- OhMyAPI is a Django-flavored web-application scaffolding framework and management layer.
38
- Built around FastAPI and TortoiseORM, it is 100% async.
37
+ OhMyAPI is a Django-flavored web-application scaffolding framework and management layer,
38
+ built around FastAPI and TortoiseORM and is thus 100% async.
39
39
 
40
- It is ***blazingly fast***, ***fun to use*** and comes with ***batteries included***!
40
+ It is ***blazingly fast***, extremely ***fun to use*** and comes with ***batteries included***!
41
41
 
42
42
  **Features**
43
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
44
+ - Django-like project structure and application directories
45
+ - Django-like per-app migrations (`makemigrations` & `migrate`) via Aerich
50
46
  - Django-like CLI tooling (`startproject`, `startapp`, `shell`, `serve`, etc)
51
- - Various optional builtin apps you can hook into your project
47
+ - Customizable pydantic model serializer built-in
48
+ - Various optional built-in apps you can hook into your project
52
49
  - Highly configurable and customizable
53
50
  - 100% async
54
51
 
55
52
  **Goals**
56
53
 
57
- - combine FastAPI, TortoiseORM and Aerich migrations into a high-productivity web-application framework
58
- - tie everything neatly together into a concise API
59
- - while ***AVOIDING*** any additional abstractions ontop of Tortoise's model-system or FastAPI's routing system
54
+ - combine `FastAPI`, `TortoiseORM`, `Aerich` migrations and `Pydantic` into a high-productivity web-application framework
55
+ - tie everything neatly together into a concise and straight-forward API
56
+ - ***AVOID*** adding any abstractions on top, unless they make things extremely convenient
60
57
 
61
58
  ---
62
59
 
@@ -3,7 +3,10 @@ ohmyapi/__main__.py,sha256=wcCrL4PjG51r5wVKqJhcoJPTLfHW0wNbD31DrUN0MWI,28
3
3
  ohmyapi/builtin/auth/__init__.py,sha256=vOVCSJX8BALzs8h5ZW9507bjoscP37bncMjdMmBXcMM,42
4
4
  ohmyapi/builtin/auth/models.py,sha256=Fggg3GDVydKoZQOlXXNDsWKxehvsp8BXC1xedv0Qr34,1729
5
5
  ohmyapi/builtin/auth/permissions.py,sha256=mxsnhF_UGesTFle7v1JHORkNODtQ0qanAL3FtOcMCEY,145
6
- ohmyapi/builtin/auth/routes.py,sha256=faVs9FuldubEcA3N335OvNGwiMUBjGxHf4IzIUQhV8o,5744
6
+ ohmyapi/builtin/auth/routes.py,sha256=rhQJ1GnBJctB6jrKAl415bwoaMFypDqoVQ9ZpHyi9v4,6312
7
+ ohmyapi/builtin/demo/__init__.py,sha256=k1rGtOmMPVZJ1fMPELY0v3k70WyzSp18pstJTkCdFr0,42
8
+ ohmyapi/builtin/demo/models.py,sha256=N3LnHLEa5wYBvaQBImCR4SdZvRYGuwM_iyLCeh9QY8s,1403
9
+ ohmyapi/builtin/demo/routes.py,sha256=DQ-wO9O0lTJkYqAjVSp0jBs9pavs5vS9IlHXRePU-Ng,1806
7
10
  ohmyapi/cli.py,sha256=dJVNgpW5S4rCc619AEEKBKuEIAmQs153Ls0ZVaea48w,4173
8
11
  ohmyapi/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
12
  ohmyapi/core/runtime.py,sha256=i4dI7mC59rw0blMOeVGawUm8v5id3ZllLlP8RLo1GT0,9419
@@ -12,14 +15,14 @@ ohmyapi/core/templates/app/__init__.py.j2,sha256=QwVIQVUGZVhdH1d4NrvL7NTsK4-T4ci
12
15
  ohmyapi/core/templates/app/models.py.j2,sha256=_3w-vFJ5fgsmncsCv34k_wyCMF78jufbSSglns4gbb0,119
13
16
  ohmyapi/core/templates/app/routes.py.j2,sha256=dFpmfrfN1pwOsD6MAa_MmI7aP4kKJ2ZiijobWHsfyDs,873
14
17
  ohmyapi/core/templates/project/README.md.j2,sha256=SjR4JIrg-8XRE-UntUDwiw8jDpYitD_UjwoKkYJ7GLw,22
15
- ohmyapi/core/templates/project/pyproject.toml.j2,sha256=X0VS6YT9aL3vpHFKPTfLFsdpD8423nY57ySQpSTMxmQ,895
18
+ ohmyapi/core/templates/project/pyproject.toml.j2,sha256=3ZqO6FX9Bhq8OAZl8nHPXCiWxl3gAffIF-LsC_-K9Us,538
16
19
  ohmyapi/core/templates/project/settings.py.j2,sha256=RBKGB8MZWPM3Bp0a57Y1YrSvSXxh502TUnJqbbu48Ig,138
17
20
  ohmyapi/db/__init__.py,sha256=5QKUycxnN83DOUD_Etoee9tEOYjnZ74deqrSOOx_MiQ,204
18
21
  ohmyapi/db/exceptions.py,sha256=vb4IIUoeYAY6sK42zRtjMy-39IFVi_Qb6mWySTY0jYw,34
19
22
  ohmyapi/db/model/__init__.py,sha256=k3StTNuKatpwZo_Z5JBFa-927eJrzibFE8U4SA82asc,32
20
23
  ohmyapi/db/model/model.py,sha256=WTf41ByCtfk9c_O6QCsO9KA0avHL3zGMZ6SEdw5GOuc,2420
21
- ohmyapi/router.py,sha256=LDxOyiqSb5zVU9zCxi014Ad4DKPZn6V8o9u0WfpmgdE,83
22
- ohmyapi-0.1.22.dist-info/METADATA,sha256=NrxMn9DH5ydLr6Aui5yN4svdRP9UiuKT5GqzJ8XMuaU,9727
23
- ohmyapi-0.1.22.dist-info/WHEEL,sha256=M5asmiAlL6HEcOq52Yi5mmk9KmTVjY2RDPtO4p9DMrc,88
24
- ohmyapi-0.1.22.dist-info/entry_points.txt,sha256=wb3lw8-meAlpiv1mqcQ3m25ukL7djagU_w89GkrC37k,43
25
- ohmyapi-0.1.22.dist-info/RECORD,,
24
+ ohmyapi/router.py,sha256=6Exv6sVPVyiIYxxAQbxQhFRX74MKTUPWXIBwC7UZ-ww,82
25
+ ohmyapi-0.1.24.dist-info/METADATA,sha256=YbyF-6_5QGYVomyCh_BJWzN8IKGqBBTQ6O342yjwY8E,9626
26
+ ohmyapi-0.1.24.dist-info/WHEEL,sha256=M5asmiAlL6HEcOq52Yi5mmk9KmTVjY2RDPtO4p9DMrc,88
27
+ ohmyapi-0.1.24.dist-info/entry_points.txt,sha256=wb3lw8-meAlpiv1mqcQ3m25ukL7djagU_w89GkrC37k,43
28
+ ohmyapi-0.1.24.dist-info/RECORD,,