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.
- ohmyapi/builtin/auth/routes.py +67 -42
- ohmyapi/builtin/demo/__init__.py +2 -0
- ohmyapi/builtin/demo/models.py +50 -0
- ohmyapi/builtin/demo/routes.py +57 -0
- ohmyapi/core/templates/project/pyproject.toml.j2 +1 -12
- ohmyapi/router.py +0 -1
- {ohmyapi-0.1.22.dist-info → ohmyapi-0.1.24.dist-info}/METADATA +11 -14
- {ohmyapi-0.1.22.dist-info → ohmyapi-0.1.24.dist-info}/RECORD +10 -7
- {ohmyapi-0.1.22.dist-info → ohmyapi-0.1.24.dist-info}/WHEEL +0 -0
- {ohmyapi-0.1.22.dist-info → ohmyapi-0.1.24.dist-info}/entry_points.txt +0 -0
ohmyapi/builtin/auth/routes.py
CHANGED
@@ -26,9 +26,62 @@ REFRESH_TOKEN_EXPIRE_SECONDS = getattr(
|
|
26
26
|
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/auth/login")
|
27
27
|
|
28
28
|
|
29
|
-
|
30
|
-
|
31
|
-
|
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
|
-
|
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
|
-
"
|
153
|
-
|
154
|
-
|
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
|
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.
|
213
|
+
return await User.Schema.model.from_tortoise_orm(user)
|
@@ -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
|
-
"
|
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,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: ohmyapi
|
3
|
-
Version: 0.1.
|
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
|
-
|
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
|
45
|
-
- Django-like
|
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
|
-
-
|
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
|
58
|
-
- tie everything neatly together into a concise API
|
59
|
-
-
|
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=
|
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=
|
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=
|
22
|
-
ohmyapi-0.1.
|
23
|
-
ohmyapi-0.1.
|
24
|
-
ohmyapi-0.1.
|
25
|
-
ohmyapi-0.1.
|
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,,
|
File without changes
|
File without changes
|