ohmyapi 0.1.22__py3-none-any.whl → 0.1.23__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,64 @@ 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(
68
+ token_type: TokenType, user: User, groups: List[Group] = []
69
+ ) -> Claims:
70
+ return Claims(
71
+ type=token_type,
72
+ sub=str(user.id),
73
+ user=ClaimsUser(
74
+ username=user.username,
75
+ email=user.email,
76
+ is_admin=user.is_admin,
77
+ is_staff=user.is_staff,
78
+ ),
79
+ roles=[g.name for g in groups],
80
+ exp="",
81
+ )
82
+
83
+
84
+ def create_token(claims: Claims, expires_in: int) -> str:
85
+ to_encode = claims.model_dump()
86
+ to_encode['exp'] = int(time.time()) + expires_in
32
87
  token = jwt.encode(to_encode, JWT_SECRET, algorithm=JWT_ALGORITHM)
33
88
  if isinstance(token, bytes):
34
89
  token = token.decode("utf-8")
@@ -48,29 +103,6 @@ def decode_token(token: str) -> Dict:
48
103
  )
49
104
 
50
105
 
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
106
  async def get_token(token: str = Depends(oauth2_scheme)) -> Dict:
75
107
  """Dependency: token introspection"""
76
108
  payload = decode_token(token)
@@ -127,12 +159,7 @@ async def require_group(
127
159
  return current_user
128
160
 
129
161
 
130
- class LoginRequest(BaseModel):
131
- username: str
132
- password: str
133
-
134
-
135
- @router.post("/login")
162
+ @router.post("/login", response_model=RefreshToken)
136
163
  async def login(form_data: LoginRequest = Body(...)):
137
164
  """Login with username & password, returns access and refresh tokens."""
138
165
  user = await User.authenticate(form_data.username, form_data.password)
@@ -148,14 +175,14 @@ async def login(form_data: LoginRequest = Body(...)):
148
175
  claims(TokenType.refresh, user), REFRESH_TOKEN_EXPIRE_SECONDS
149
176
  )
150
177
 
151
- return {
152
- "access_token": access_token,
153
- "refresh_token": refresh_token,
154
- "token_type": "bearer",
155
- }
178
+ return RefreshToken(
179
+ token_type="bearer",
180
+ access_token=access_token,
181
+ refresh_token=refresh_token,
182
+ )
156
183
 
157
184
 
158
- @router.post("/refresh")
185
+ @router.post("/refresh", response_model=AccessToken)
159
186
  async def refresh_token(refresh_token: str):
160
187
  """Exchange refresh token for new access token."""
161
188
  payload = decode_token(refresh_token)
@@ -174,15 +201,15 @@ async def refresh_token(refresh_token: str):
174
201
  new_access = create_token(
175
202
  claims(TokenType.access, user), ACCESS_TOKEN_EXPIRE_SECONDS
176
203
  )
177
- return {"access_token": new_access, "token_type": "bearer"}
204
+ return AccessToken(token_type="bearer", access_token=access_token)
178
205
 
179
206
 
180
- @router.get("/introspect")
207
+ @router.get("/introspect", response_model=Dict[str, Any])
181
208
  async def introspect(token: Dict = Depends(get_token)):
182
209
  return token
183
210
 
184
211
 
185
- @router.get("/me")
212
+ @router.get("/me", response_model=User.Schema.model)
186
213
  async def me(user: User = Depends(get_current_user)):
187
214
  """Return the currently authenticated user."""
188
- return User.Schema.one.from_orm(user)
215
+ 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,54 @@
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"],
16
+ response_model=List[models.Tournament.Schema.model])
17
+ async def list():
18
+ """List all tournaments."""
19
+ return await models.Tournament.Schema.model.from_queryset(Tournament.all())
20
+
21
+
22
+ @router.post("/",
23
+ tags=["tournament"],
24
+ status_code=HTTPStatus.CREATED)
25
+ async def post(tournament: models.Tournament.Schema.readonly):
26
+ """Create tournament."""
27
+ return await models.Tournament.Schema.model.from_queryset(models.Tournament.create(**tournament.model_dump()))
28
+
29
+
30
+ @router.get("/{id}",
31
+ tags=["tournament"],
32
+ response_model=models.Tournament.Schema.model)
33
+ async def get(id: str):
34
+ """Get tournament by id."""
35
+ return await models.Tournament.Schema.model.from_queryset(models.Tournament.get(id=id))
36
+
37
+
38
+ @router.put("/{id}",
39
+ tags=["tournament"],
40
+ response_model=models.Tournament.Schema.model,
41
+ status_code=HTTPStatus.ACCEPTED)
42
+ async def put(tournament: models.Tournament.Schema.model):
43
+ """Update tournament."""
44
+ return await models.Tournament.Schema.model.from_queryset(models.Tournament.update(**tournament.model_dump()))
45
+
46
+
47
+ @router.delete("/{id}", tags=["tournament"])
48
+ async def delete(id: str):
49
+ try:
50
+ tournament = await models.Tournament.get(id=id)
51
+ return await tournament.delete()
52
+ except DoesNotExist:
53
+ raise HTTPException(status_code=HTTPStatus.NOT_FOUND, detail="not found")
54
+
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.23
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
@@ -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=e6i9AanJqj1RK8VQVXwNV-Pv8R3X8I70SgaDtbCYUIE,6318
7
+ ohmyapi/builtin/demo/__init__.py,sha256=k1rGtOmMPVZJ1fMPELY0v3k70WyzSp18pstJTkCdFr0,42
8
+ ohmyapi/builtin/demo/models.py,sha256=8Id0dHPuow_Dtt_337UV3VTxvtVAllaVeImvstqiqdE,1403
9
+ ohmyapi/builtin/demo/routes.py,sha256=MQaPCz43v4kTv9_oz44up1j77Ra1SqSN9i3vN26xllU,1843
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
@@ -18,8 +21,8 @@ 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.23.dist-info/METADATA,sha256=HPmvXFChCOfKmKOn6u6JlcItr_2_UrXk9MoA7aCBl2Y,9727
26
+ ohmyapi-0.1.23.dist-info/WHEEL,sha256=M5asmiAlL6HEcOq52Yi5mmk9KmTVjY2RDPtO4p9DMrc,88
27
+ ohmyapi-0.1.23.dist-info/entry_points.txt,sha256=wb3lw8-meAlpiv1mqcQ3m25ukL7djagU_w89GkrC37k,43
28
+ ohmyapi-0.1.23.dist-info/RECORD,,