ohmyapi 0.1.19__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.
Files changed (24) hide show
  1. {ohmyapi-0.1.19 → ohmyapi-0.1.20}/PKG-INFO +21 -13
  2. {ohmyapi-0.1.19 → ohmyapi-0.1.20}/README.md +20 -12
  3. {ohmyapi-0.1.19 → ohmyapi-0.1.20}/pyproject.toml +1 -1
  4. {ohmyapi-0.1.19 → ohmyapi-0.1.20}/src/ohmyapi/builtin/auth/models.py +6 -4
  5. {ohmyapi-0.1.19 → ohmyapi-0.1.20}/src/ohmyapi/cli.py +26 -31
  6. {ohmyapi-0.1.19 → ohmyapi-0.1.20}/src/ohmyapi/db/model/model.py +21 -0
  7. {ohmyapi-0.1.19 → ohmyapi-0.1.20}/src/ohmyapi/__init__.py +0 -0
  8. {ohmyapi-0.1.19 → ohmyapi-0.1.20}/src/ohmyapi/__main__.py +0 -0
  9. {ohmyapi-0.1.19 → ohmyapi-0.1.20}/src/ohmyapi/builtin/auth/__init__.py +0 -0
  10. {ohmyapi-0.1.19 → ohmyapi-0.1.20}/src/ohmyapi/builtin/auth/permissions.py +0 -0
  11. {ohmyapi-0.1.19 → ohmyapi-0.1.20}/src/ohmyapi/builtin/auth/routes.py +0 -0
  12. {ohmyapi-0.1.19 → ohmyapi-0.1.20}/src/ohmyapi/core/__init__.py +0 -0
  13. {ohmyapi-0.1.19 → ohmyapi-0.1.20}/src/ohmyapi/core/runtime.py +0 -0
  14. {ohmyapi-0.1.19 → ohmyapi-0.1.20}/src/ohmyapi/core/scaffolding.py +0 -0
  15. {ohmyapi-0.1.19 → ohmyapi-0.1.20}/src/ohmyapi/core/templates/app/__init__.py.j2 +0 -0
  16. {ohmyapi-0.1.19 → ohmyapi-0.1.20}/src/ohmyapi/core/templates/app/models.py.j2 +0 -0
  17. {ohmyapi-0.1.19 → ohmyapi-0.1.20}/src/ohmyapi/core/templates/app/routes.py.j2 +0 -0
  18. {ohmyapi-0.1.19 → ohmyapi-0.1.20}/src/ohmyapi/core/templates/project/README.md.j2 +0 -0
  19. {ohmyapi-0.1.19 → ohmyapi-0.1.20}/src/ohmyapi/core/templates/project/pyproject.toml.j2 +0 -0
  20. {ohmyapi-0.1.19 → ohmyapi-0.1.20}/src/ohmyapi/core/templates/project/settings.py.j2 +0 -0
  21. {ohmyapi-0.1.19 → ohmyapi-0.1.20}/src/ohmyapi/db/__init__.py +0 -0
  22. {ohmyapi-0.1.19 → ohmyapi-0.1.20}/src/ohmyapi/db/exceptions.py +0 -0
  23. {ohmyapi-0.1.19 → ohmyapi-0.1.20}/src/ohmyapi/db/model/__init__.py +0 -0
  24. {ohmyapi-0.1.19 → 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.19
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,6 +1,6 @@
1
1
  [project]
2
2
  name = "ohmyapi"
3
- version = "0.1.19"
3
+ version = "0.1.20"
4
4
  description = "A Django-like but async web-framework based on FastAPI and TortoiseORM."
5
5
  license = "MIT"
6
6
  keywords = ["fastapi", "tortoise", "orm", "async", "web-framework"]
@@ -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: str = field.data.UUIDField(pk=True)
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: str = field.data.UUIDField(pk=True)
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
- # Ensure the ORM is shutdown
50
- async def close_project():
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
- def cleanup():
58
- loop = None
59
- try:
60
- loop = asyncio.get_running_loop()
61
- except RuntimeError:
62
- pass
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.format(**{
80
- "project_name": f"{f'{project.settings.PROJECT_NAME} ' if getattr(project.settings, 'PROJECT_NAME', '') else ''}[{Path(project_path).resolve()}]",
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
- atexit.register(cleanup)
88
- code.interact(local={"p": project})
81
+ code.interact(local=shell_vars, banner=banner)
82
+ finally:
83
+ loop.run_until_complete(cleanup())
89
84
 
90
85
 
91
86
  @app.command()
@@ -1,6 +1,27 @@
1
+ from pydantic_core import core_schema
2
+ from pydantic import GetCoreSchemaHandler
1
3
  from tortoise import fields as field
2
4
  from tortoise.models import Model as TortoiseModel
3
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)
4
25
 
5
26
 
6
27
  class ModelMeta(type(TortoiseModel)):
File without changes