ohmyapi 0.1.18__tar.gz → 0.1.20__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.
- {ohmyapi-0.1.18 → ohmyapi-0.1.20}/PKG-INFO +21 -13
- {ohmyapi-0.1.18 → ohmyapi-0.1.20}/README.md +20 -12
- {ohmyapi-0.1.18 → ohmyapi-0.1.20}/pyproject.toml +1 -1
- {ohmyapi-0.1.18 → ohmyapi-0.1.20}/src/ohmyapi/builtin/auth/models.py +6 -4
- {ohmyapi-0.1.18 → ohmyapi-0.1.20}/src/ohmyapi/cli.py +26 -31
- ohmyapi-0.1.20/src/ohmyapi/db/model/model.py +67 -0
- ohmyapi-0.1.18/src/ohmyapi/db/model/model.py +0 -34
- {ohmyapi-0.1.18 → ohmyapi-0.1.20}/src/ohmyapi/__init__.py +0 -0
- {ohmyapi-0.1.18 → ohmyapi-0.1.20}/src/ohmyapi/__main__.py +0 -0
- {ohmyapi-0.1.18 → ohmyapi-0.1.20}/src/ohmyapi/builtin/auth/__init__.py +0 -0
- {ohmyapi-0.1.18 → ohmyapi-0.1.20}/src/ohmyapi/builtin/auth/permissions.py +0 -0
- {ohmyapi-0.1.18 → ohmyapi-0.1.20}/src/ohmyapi/builtin/auth/routes.py +0 -0
- {ohmyapi-0.1.18 → ohmyapi-0.1.20}/src/ohmyapi/core/__init__.py +0 -0
- {ohmyapi-0.1.18 → ohmyapi-0.1.20}/src/ohmyapi/core/runtime.py +0 -0
- {ohmyapi-0.1.18 → ohmyapi-0.1.20}/src/ohmyapi/core/scaffolding.py +0 -0
- {ohmyapi-0.1.18 → ohmyapi-0.1.20}/src/ohmyapi/core/templates/app/__init__.py.j2 +0 -0
- {ohmyapi-0.1.18 → ohmyapi-0.1.20}/src/ohmyapi/core/templates/app/models.py.j2 +0 -0
- {ohmyapi-0.1.18 → ohmyapi-0.1.20}/src/ohmyapi/core/templates/app/routes.py.j2 +0 -0
- {ohmyapi-0.1.18 → ohmyapi-0.1.20}/src/ohmyapi/core/templates/project/README.md.j2 +0 -0
- {ohmyapi-0.1.18 → ohmyapi-0.1.20}/src/ohmyapi/core/templates/project/pyproject.toml.j2 +0 -0
- {ohmyapi-0.1.18 → ohmyapi-0.1.20}/src/ohmyapi/core/templates/project/settings.py.j2 +0 -0
- {ohmyapi-0.1.18 → ohmyapi-0.1.20}/src/ohmyapi/db/__init__.py +0 -0
- {ohmyapi-0.1.18 → ohmyapi-0.1.20}/src/ohmyapi/db/exceptions.py +0 -0
- {ohmyapi-0.1.18 → ohmyapi-0.1.20}/src/ohmyapi/db/model/__init__.py +0 -0
- {ohmyapi-0.1.18 → ohmyapi-0.1.20}/src/ohmyapi/router.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: ohmyapi
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.20
|
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
|
@@ -118,31 +118,35 @@ Write your first model in `turnament/models.py`:
|
|
118
118
|
```python
|
119
119
|
from ohmyapi.db import Model, field
|
120
120
|
|
121
|
+
from datetime import datetime
|
122
|
+
from decimal import Decimal
|
123
|
+
from uuid import UUID
|
124
|
+
|
121
125
|
|
122
126
|
class Tournament(Model):
|
123
|
-
id = field.data.UUIDField(primary_key=True)
|
124
|
-
name = field.TextField()
|
125
|
-
created = field.DatetimeField(auto_now_add=True)
|
127
|
+
id: UUID = field.data.UUIDField(primary_key=True)
|
128
|
+
name: str = field.TextField()
|
129
|
+
created: datetime = field.DatetimeField(auto_now_add=True)
|
126
130
|
|
127
131
|
def __str__(self):
|
128
132
|
return self.name
|
129
133
|
|
130
134
|
|
131
135
|
class Event(Model):
|
132
|
-
id = field.data.UUIDField(primary_key=True)
|
133
|
-
name = field.TextField()
|
134
|
-
tournament = field.ForeignKeyField('tournament.Tournament', related_name='events')
|
135
|
-
participants = field.ManyToManyField('tournament.Team', related_name='events', through='event_team')
|
136
|
-
modified = field.DatetimeField(auto_now=True)
|
137
|
-
prize = field.DecimalField(max_digits=10, decimal_places=2, null=True)
|
136
|
+
id: UUID = field.data.UUIDField(primary_key=True)
|
137
|
+
name: str = field.TextField()
|
138
|
+
tournament: UUID = field.ForeignKeyField('tournament.Tournament', related_name='events')
|
139
|
+
participants: field.ManyToManyRelation[Team] = field.ManyToManyField('tournament.Team', related_name='events', through='event_team')
|
140
|
+
modified: datetime = field.DatetimeField(auto_now=True)
|
141
|
+
prize: Decimal = field.DecimalField(max_digits=10, decimal_places=2, null=True)
|
138
142
|
|
139
143
|
def __str__(self):
|
140
144
|
return self.name
|
141
145
|
|
142
146
|
|
143
147
|
class Team(Model):
|
144
|
-
id = field.data.UUIDField(primary_key=True)
|
145
|
-
name = field.TextField()
|
148
|
+
id: UUID = field.data.UUIDField(primary_key=True)
|
149
|
+
name: str = field.TextField()
|
146
150
|
|
147
151
|
def __str__(self):
|
148
152
|
return self.name
|
@@ -242,9 +246,13 @@ It comes with a `User` and `Group` model, as well as endpoints for JWT auth.
|
|
242
246
|
You can use the models as `ForeignKeyField` in your application models:
|
243
247
|
|
244
248
|
```python
|
249
|
+
from ohmyapi.db import Model, field
|
250
|
+
from ohmyapi_auth.models import User
|
251
|
+
|
252
|
+
|
245
253
|
class Team(Model):
|
246
254
|
[...]
|
247
|
-
members = field.ManyToManyField('ohmyapi_auth.User', related_name='tournament_teams', through='tournament_teams')
|
255
|
+
members: field.ManyToManyRelation[User] = field.ManyToManyField('ohmyapi_auth.User', related_name='tournament_teams', through='tournament_teams')
|
248
256
|
[...]
|
249
257
|
```
|
250
258
|
|
@@ -86,31 +86,35 @@ Write your first model in `turnament/models.py`:
|
|
86
86
|
```python
|
87
87
|
from ohmyapi.db import Model, field
|
88
88
|
|
89
|
+
from datetime import datetime
|
90
|
+
from decimal import Decimal
|
91
|
+
from uuid import UUID
|
92
|
+
|
89
93
|
|
90
94
|
class Tournament(Model):
|
91
|
-
id = field.data.UUIDField(primary_key=True)
|
92
|
-
name = field.TextField()
|
93
|
-
created = field.DatetimeField(auto_now_add=True)
|
95
|
+
id: UUID = field.data.UUIDField(primary_key=True)
|
96
|
+
name: str = field.TextField()
|
97
|
+
created: datetime = field.DatetimeField(auto_now_add=True)
|
94
98
|
|
95
99
|
def __str__(self):
|
96
100
|
return self.name
|
97
101
|
|
98
102
|
|
99
103
|
class Event(Model):
|
100
|
-
id = field.data.UUIDField(primary_key=True)
|
101
|
-
name = field.TextField()
|
102
|
-
tournament = field.ForeignKeyField('tournament.Tournament', related_name='events')
|
103
|
-
participants = field.ManyToManyField('tournament.Team', related_name='events', through='event_team')
|
104
|
-
modified = field.DatetimeField(auto_now=True)
|
105
|
-
prize = field.DecimalField(max_digits=10, decimal_places=2, null=True)
|
104
|
+
id: UUID = field.data.UUIDField(primary_key=True)
|
105
|
+
name: str = field.TextField()
|
106
|
+
tournament: UUID = field.ForeignKeyField('tournament.Tournament', related_name='events')
|
107
|
+
participants: field.ManyToManyRelation[Team] = field.ManyToManyField('tournament.Team', related_name='events', through='event_team')
|
108
|
+
modified: datetime = field.DatetimeField(auto_now=True)
|
109
|
+
prize: Decimal = field.DecimalField(max_digits=10, decimal_places=2, null=True)
|
106
110
|
|
107
111
|
def __str__(self):
|
108
112
|
return self.name
|
109
113
|
|
110
114
|
|
111
115
|
class Team(Model):
|
112
|
-
id = field.data.UUIDField(primary_key=True)
|
113
|
-
name = field.TextField()
|
116
|
+
id: UUID = field.data.UUIDField(primary_key=True)
|
117
|
+
name: str = field.TextField()
|
114
118
|
|
115
119
|
def __str__(self):
|
116
120
|
return self.name
|
@@ -210,9 +214,13 @@ It comes with a `User` and `Group` model, as well as endpoints for JWT auth.
|
|
210
214
|
You can use the models as `ForeignKeyField` in your application models:
|
211
215
|
|
212
216
|
```python
|
217
|
+
from ohmyapi.db import Model, field
|
218
|
+
from ohmyapi_auth.models import User
|
219
|
+
|
220
|
+
|
213
221
|
class Team(Model):
|
214
222
|
[...]
|
215
|
-
members = field.ManyToManyField('ohmyapi_auth.User', related_name='tournament_teams', through='tournament_teams')
|
223
|
+
members: field.ManyToManyRelation[User] = field.ManyToManyField('ohmyapi_auth.User', related_name='tournament_teams', through='tournament_teams')
|
216
224
|
[...]
|
217
225
|
```
|
218
226
|
|
@@ -1,20 +1,22 @@
|
|
1
|
-
from functools import wraps
|
2
|
-
from typing import Optional, List
|
3
1
|
from ohmyapi.router import HTTPException
|
4
2
|
from ohmyapi.db import Model, field, pre_save, pre_delete
|
3
|
+
|
4
|
+
from functools import wraps
|
5
|
+
from typing import Optional, List
|
5
6
|
from passlib.context import CryptContext
|
6
7
|
from tortoise.contrib.pydantic import pydantic_queryset_creator
|
8
|
+
from uuid import UUID
|
7
9
|
|
8
10
|
pwd_context = CryptContext(schemes=["argon2"], deprecated="auto")
|
9
11
|
|
10
12
|
|
11
13
|
class Group(Model):
|
12
|
-
id:
|
14
|
+
id: UUID = field.data.UUIDField(pk=True)
|
13
15
|
name: str = field.CharField(max_length=42, index=True)
|
14
16
|
|
15
17
|
|
16
18
|
class User(Model):
|
17
|
-
id:
|
19
|
+
id: UUID = field.data.UUIDField(pk=True)
|
18
20
|
email: str = field.CharField(max_length=255, unique=True, index=True)
|
19
21
|
username: str = field.CharField(max_length=150, unique=True)
|
20
22
|
password_hash: str = field.CharField(max_length=128)
|
@@ -10,9 +10,6 @@ from ohmyapi.core import scaffolding, runtime
|
|
10
10
|
from pathlib import Path
|
11
11
|
|
12
12
|
app = typer.Typer(help="OhMyAPI — Django-flavored FastAPI scaffolding with tightly integrated TortoiseORM.")
|
13
|
-
banner = """OhMyAPI Shell | Project: {project_name}
|
14
|
-
Find your loaded project singleton via identifier: `p`
|
15
|
-
"""
|
16
13
|
|
17
14
|
|
18
15
|
@app.command()
|
@@ -40,52 +37,50 @@ def serve(root: str = ".", host="127.0.0.1", port=8000):
|
|
40
37
|
|
41
38
|
@app.command()
|
42
39
|
def shell(root: str = "."):
|
43
|
-
"""
|
44
|
-
Launch an interactive IPython shell with the project and apps loaded.
|
45
|
-
"""
|
46
40
|
project_path = Path(root).resolve()
|
47
41
|
project = runtime.Project(project_path)
|
48
42
|
|
49
|
-
|
50
|
-
|
43
|
+
banner = f"""
|
44
|
+
OhMyAPI Project Shell: {getattr(project.settings, 'PROJECT_NAME', 'MyProject')}
|
45
|
+
Find your loaded project singleton via identifier: `p`; i.e.: `p.apps`
|
46
|
+
"""
|
47
|
+
|
48
|
+
async def init_and_cleanup():
|
49
|
+
try:
|
50
|
+
await project.init_orm()
|
51
|
+
return True
|
52
|
+
except Exception as e:
|
53
|
+
print(f"Failed to initialize ORM: {e}")
|
54
|
+
return False
|
55
|
+
|
56
|
+
async def cleanup():
|
51
57
|
try:
|
52
58
|
await project.close_orm()
|
53
59
|
print("Tortoise ORM closed successfully.")
|
54
60
|
except Exception as e:
|
55
61
|
print(f"Error closing ORM: {e}")
|
56
62
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
if loop and loop.is_running():
|
64
|
-
asyncio.create_task(close_project())
|
65
|
-
else:
|
66
|
-
asyncio.run(close_project())
|
67
|
-
|
68
|
-
# Ensure the ORM is initialized
|
69
|
-
asyncio.run(project.init_orm())
|
63
|
+
loop = asyncio.new_event_loop()
|
64
|
+
asyncio.set_event_loop(loop)
|
65
|
+
loop.run_until_complete(init_and_cleanup())
|
66
|
+
|
67
|
+
# Prepare shell vars that are to be directly available
|
68
|
+
shell_vars = {"p": project}
|
70
69
|
|
71
70
|
try:
|
72
71
|
from IPython import start_ipython
|
73
|
-
shell_vars = {
|
74
|
-
"p": project,
|
75
|
-
}
|
76
72
|
from traitlets.config.loader import Config
|
73
|
+
|
77
74
|
c = Config()
|
78
75
|
c.TerminalIPythonApp.display_banner = True
|
79
|
-
c.TerminalInteractiveShell.banner2 = banner
|
80
|
-
|
81
|
-
})
|
82
|
-
atexit.register(cleanup)
|
76
|
+
c.TerminalInteractiveShell.banner2 = banner
|
77
|
+
|
83
78
|
start_ipython(argv=[], user_ns=shell_vars, config=c)
|
84
79
|
except ImportError:
|
85
|
-
typer.echo("IPython is not installed. Falling back to built-in Python shell.")
|
86
80
|
import code
|
87
|
-
|
88
|
-
|
81
|
+
code.interact(local=shell_vars, banner=banner)
|
82
|
+
finally:
|
83
|
+
loop.run_until_complete(cleanup())
|
89
84
|
|
90
85
|
|
91
86
|
@app.command()
|
@@ -0,0 +1,67 @@
|
|
1
|
+
from pydantic_core import core_schema
|
2
|
+
from pydantic import GetCoreSchemaHandler
|
3
|
+
from tortoise import fields as field
|
4
|
+
from tortoise.models import Model as TortoiseModel
|
5
|
+
from tortoise.contrib.pydantic import pydantic_model_creator, pydantic_queryset_creator
|
6
|
+
from uuid import UUID
|
7
|
+
|
8
|
+
|
9
|
+
def __uuid_schema_monkey_patch(cls, source_type, handler):
|
10
|
+
# Always treat UUID as string schema
|
11
|
+
return core_schema.no_info_after_validator_function(
|
12
|
+
# Accept UUID or str, always return UUID internally
|
13
|
+
lambda v: v if isinstance(v, UUID) else UUID(str(v)),
|
14
|
+
core_schema.union_schema([
|
15
|
+
core_schema.str_schema(),
|
16
|
+
core_schema.is_instance_schema(UUID),
|
17
|
+
]),
|
18
|
+
# But when serializing, always str()
|
19
|
+
serialization=core_schema.plain_serializer_function_ser_schema(str, when_used="always"),
|
20
|
+
)
|
21
|
+
|
22
|
+
|
23
|
+
# Monkey-patch UUID
|
24
|
+
UUID.__get_pydantic_core_schema__ = classmethod(__uuid_schema_monkey_patch)
|
25
|
+
|
26
|
+
|
27
|
+
class ModelMeta(type(TortoiseModel)):
|
28
|
+
def __new__(cls, name, bases, attrs):
|
29
|
+
new_cls = super().__new__(cls, name, bases, attrs)
|
30
|
+
|
31
|
+
schema_opts = getattr(new_cls, "Schema", None)
|
32
|
+
|
33
|
+
class BoundSchema:
|
34
|
+
@property
|
35
|
+
def model(self):
|
36
|
+
"""Return a Pydantic model class for serializing results."""
|
37
|
+
include = getattr(schema_opts, "include", None)
|
38
|
+
exclude = getattr(schema_opts, "exclude", None)
|
39
|
+
return pydantic_model_creator(
|
40
|
+
new_cls,
|
41
|
+
name=f"{new_cls.__name__}Schema",
|
42
|
+
include=include,
|
43
|
+
exclude=exclude,
|
44
|
+
)
|
45
|
+
|
46
|
+
@property
|
47
|
+
def readonly(self):
|
48
|
+
"""Return a Pydantic model class for serializing readonly results."""
|
49
|
+
include = getattr(schema_opts, "include", None)
|
50
|
+
exclude = getattr(schema_opts, "exclude", None)
|
51
|
+
return pydantic_model_creator(
|
52
|
+
new_cls,
|
53
|
+
name=f"{new_cls.__name__}SchemaReadonly",
|
54
|
+
include=include,
|
55
|
+
exclude=exclude,
|
56
|
+
exclude_readonly=True,
|
57
|
+
)
|
58
|
+
|
59
|
+
new_cls.Schema = BoundSchema()
|
60
|
+
return new_cls
|
61
|
+
|
62
|
+
|
63
|
+
class Model(TortoiseModel, metaclass=ModelMeta):
|
64
|
+
class Schema:
|
65
|
+
include = None
|
66
|
+
exclude = None
|
67
|
+
|
@@ -1,34 +0,0 @@
|
|
1
|
-
from tortoise import fields as field
|
2
|
-
from tortoise.models import Model as TortoiseModel
|
3
|
-
from tortoise.contrib.pydantic import pydantic_model_creator, pydantic_queryset_creator
|
4
|
-
|
5
|
-
|
6
|
-
class ModelMeta(type(TortoiseModel)):
|
7
|
-
def __new__(cls, name, bases, attrs):
|
8
|
-
new_cls = super().__new__(cls, name, bases, attrs)
|
9
|
-
|
10
|
-
schema_opts = getattr(new_cls, "Schema", None)
|
11
|
-
|
12
|
-
class BoundSchema:
|
13
|
-
@property
|
14
|
-
def model(self):
|
15
|
-
"""Return a Pydantic model class for 'one' results."""
|
16
|
-
include = getattr(schema_opts, "include", None)
|
17
|
-
exclude = getattr(schema_opts, "exclude", None)
|
18
|
-
return pydantic_model_creator(
|
19
|
-
new_cls,
|
20
|
-
name=f"{new_cls.__name__}Schema",
|
21
|
-
include=include,
|
22
|
-
exclude=exclude,
|
23
|
-
exclude_readonly=True,
|
24
|
-
)
|
25
|
-
|
26
|
-
new_cls.Schema = BoundSchema()
|
27
|
-
return new_cls
|
28
|
-
|
29
|
-
|
30
|
-
class Model(TortoiseModel, metaclass=ModelMeta):
|
31
|
-
class Schema:
|
32
|
-
include = None
|
33
|
-
exclude = None
|
34
|
-
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|