ohmyapi 0.3.2__py3-none-any.whl → 0.4.0__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/__init__.py +1 -1
- ohmyapi/builtin/auth/models.py +31 -4
- ohmyapi/cli.py +17 -5
- ohmyapi/core/logging.py +38 -0
- ohmyapi/core/runtime.py +42 -5
- ohmyapi/core/scaffolding.py +7 -9
- ohmyapi/core/templates/docker/Dockerfile +0 -1
- ohmyapi/core/templates/docker/docker-compose.yml +10 -0
- ohmyapi/core/templates/project/pyproject.toml.j2 +1 -1
- ohmyapi/db/__init__.py +1 -0
- ohmyapi/middleware/cors.py +26 -0
- {ohmyapi-0.3.2.dist-info → ohmyapi-0.4.0.dist-info}/METADATA +5 -4
- {ohmyapi-0.3.2.dist-info → ohmyapi-0.4.0.dist-info}/RECORD +15 -13
- {ohmyapi-0.3.2.dist-info → ohmyapi-0.4.0.dist-info}/WHEEL +0 -0
- {ohmyapi-0.3.2.dist-info → ohmyapi-0.4.0.dist-info}/entry_points.txt +0 -0
ohmyapi/__init__.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
__VERSION__ = "0.
|
1
|
+
__VERSION__ = "0.4.0"
|
ohmyapi/builtin/auth/models.py
CHANGED
@@ -1,25 +1,40 @@
|
|
1
|
+
import hmac
|
2
|
+
import hashlib
|
3
|
+
import base64
|
1
4
|
from functools import wraps
|
5
|
+
from secrets import token_bytes
|
2
6
|
from typing import List, Optional
|
3
7
|
from uuid import UUID
|
4
8
|
|
5
9
|
from passlib.context import CryptContext
|
6
10
|
from tortoise.contrib.pydantic import pydantic_queryset_creator
|
7
11
|
|
8
|
-
from ohmyapi.db import Model, field,
|
12
|
+
from ohmyapi.db import Model, field, Q
|
9
13
|
from ohmyapi.router import HTTPException
|
10
14
|
|
15
|
+
import settings
|
16
|
+
|
11
17
|
pwd_context = CryptContext(schemes=["argon2"], deprecated="auto")
|
18
|
+
SECRET_KEY = getattr(settings, "SECRET_KEY", "OhMyAPI Secret Key")
|
19
|
+
|
20
|
+
|
21
|
+
def hmac_hash(data: str) -> str:
|
22
|
+
digest = hmac.new(SECRET_KEY.encode("UTF-8"), data.encode("utf-8"), hashlib.sha256).digest()
|
23
|
+
return base64.urlsafe_b64encode(digest).decode("utf-8")
|
12
24
|
|
13
25
|
|
14
26
|
class Group(Model):
|
15
27
|
id: UUID = field.data.UUIDField(pk=True)
|
16
28
|
name: str = field.CharField(max_length=42, index=True)
|
17
29
|
|
30
|
+
def __str__(self):
|
31
|
+
return self.name if self.name else ""
|
32
|
+
|
18
33
|
|
19
34
|
class User(Model):
|
20
35
|
id: UUID = field.data.UUIDField(pk=True)
|
21
|
-
email: str = field.CharField(max_length=255, unique=True, index=True)
|
22
36
|
username: str = field.CharField(max_length=150, unique=True)
|
37
|
+
email_hash: Optional[str] = field.CharField(max_length=255, unique=True, index=True)
|
23
38
|
password_hash: str = field.CharField(max_length=128)
|
24
39
|
is_admin: bool = field.BooleanField(default=False)
|
25
40
|
is_staff: bool = field.BooleanField(default=False)
|
@@ -28,18 +43,30 @@ class User(Model):
|
|
28
43
|
)
|
29
44
|
|
30
45
|
class Schema:
|
31
|
-
exclude =
|
46
|
+
exclude = ["password_hash", "email_hash"]
|
47
|
+
|
48
|
+
def __str__(self):
|
49
|
+
fields = {
|
50
|
+
'username': self.username if self.username else "-",
|
51
|
+
'is_admin': 'y' if self.is_admin else 'n',
|
52
|
+
'is_staff': 'y' if self.is_staff else 'n',
|
53
|
+
}
|
54
|
+
return ' '.join([f"{k}:{v}" for k, v in fields.items()])
|
32
55
|
|
33
56
|
def set_password(self, raw_password: str) -> None:
|
34
57
|
"""Hash and store the password."""
|
35
58
|
self.password_hash = pwd_context.hash(raw_password)
|
36
59
|
|
60
|
+
def set_email(self, new_email: str) -> None:
|
61
|
+
"""Hash and set the e-mail address."""
|
62
|
+
self.email_hash = hmac_hash(email)
|
63
|
+
|
37
64
|
def verify_password(self, raw_password: str) -> bool:
|
38
65
|
"""Verify a plaintext password against the stored hash."""
|
39
66
|
return pwd_context.verify(raw_password, self.password_hash)
|
40
67
|
|
41
68
|
@classmethod
|
42
|
-
async def
|
69
|
+
async def authenticate_username(cls, username: str, password: str) -> Optional["User"]:
|
43
70
|
"""Authenticate a user by username and password."""
|
44
71
|
user = await cls.filter(username=username).first()
|
45
72
|
if user and user.verify_password(password):
|
ohmyapi/cli.py
CHANGED
@@ -9,6 +9,9 @@ import typer
|
|
9
9
|
import uvicorn
|
10
10
|
|
11
11
|
from ohmyapi.core import runtime, scaffolding
|
12
|
+
from ohmyapi.core.logging import setup_logging
|
13
|
+
|
14
|
+
logger = setup_logging()
|
12
15
|
|
13
16
|
app = typer.Typer(
|
14
17
|
help="OhMyAPI — Django-flavored FastAPI scaffolding with tightly integrated TortoiseORM."
|
@@ -19,18 +22,24 @@ app = typer.Typer(
|
|
19
22
|
def startproject(name: str):
|
20
23
|
"""Create a new OhMyAPI project in the given directory."""
|
21
24
|
scaffolding.startproject(name)
|
25
|
+
logger.info(f"✅ Project '{name}' created successfully.")
|
26
|
+
logger.info(f"🔧 Next, configure your project in {name}/settings.py")
|
22
27
|
|
23
28
|
|
24
29
|
@app.command()
|
25
30
|
def startapp(app_name: str, root: str = "."):
|
26
31
|
"""Create a new app with the given name in your OhMyAPI project."""
|
27
32
|
scaffolding.startapp(app_name, root)
|
33
|
+
print(f"✅ App '{app_name}' created in project '{root}' successfully.")
|
34
|
+
print(f"🔧 Remember to add '{app_name}' to your INSTALLED_APPS!")
|
28
35
|
|
29
36
|
|
30
37
|
@app.command()
|
31
38
|
def dockerize(root: str = "."):
|
32
39
|
"""Create template Dockerfile and docker-compose.yml."""
|
33
40
|
scaffolding.copy_static("docker", root)
|
41
|
+
logger.info(f"✅ Templates created successfully.")
|
42
|
+
logger.info(f"🔧 Next, run `docker compose up -d --build`")
|
34
43
|
|
35
44
|
|
36
45
|
@app.command()
|
@@ -40,9 +49,8 @@ def serve(root: str = ".", host="127.0.0.1", port=8000):
|
|
40
49
|
"""
|
41
50
|
project_path = Path(root)
|
42
51
|
project = runtime.Project(project_path)
|
43
|
-
app_instance = project.app()
|
44
|
-
app_instance =
|
45
|
-
uvicorn.run(app_instance, host=host, port=int(port), reload=False)
|
52
|
+
app_instance = project.configure_app(project.app())
|
53
|
+
uvicorn.run(app_instance, host=host, port=int(port), reload=False, log_config=None)
|
46
54
|
|
47
55
|
|
48
56
|
@app.command()
|
@@ -75,7 +83,7 @@ def shell(root: str = "."):
|
|
75
83
|
asyncio.set_event_loop(loop)
|
76
84
|
loop.run_until_complete(init_and_cleanup())
|
77
85
|
|
78
|
-
# Prepare shell vars that are to be
|
86
|
+
# Prepare shell vars that are to be immediately available
|
79
87
|
shell_vars = {"p": project}
|
80
88
|
|
81
89
|
try:
|
@@ -107,6 +115,8 @@ def makemigrations(app: str = "*", name: str = "auto", root: str = "."):
|
|
107
115
|
asyncio.run(project.makemigrations(app_label=app, name=name))
|
108
116
|
else:
|
109
117
|
asyncio.run(project.makemigrations(app_label=app, name=name))
|
118
|
+
logger.info(f"✅ Migrations created successfully.")
|
119
|
+
logger.info(f"🔧 To migrate the DB, run `ohmyapi migrate`, next.")
|
110
120
|
|
111
121
|
|
112
122
|
@app.command()
|
@@ -121,6 +131,7 @@ def migrate(app: str = "*", root: str = "."):
|
|
121
131
|
asyncio.run(project.migrate(app))
|
122
132
|
else:
|
123
133
|
asyncio.run(project.migrate(app))
|
134
|
+
logger.info(f"✅ Migrations ran successfully.")
|
124
135
|
|
125
136
|
|
126
137
|
@app.command()
|
@@ -150,8 +161,9 @@ def createsuperuser(root: str = "."):
|
|
150
161
|
if password1 != password2:
|
151
162
|
print("Passwords didn't match!")
|
152
163
|
user = ohmyapi_auth.models.User(
|
153
|
-
|
164
|
+
username=username, is_staff=True, is_admin=True
|
154
165
|
)
|
166
|
+
user.set_email(email)
|
155
167
|
user.set_password(password1)
|
156
168
|
asyncio.run(project.init_orm())
|
157
169
|
asyncio.run(user.save())
|
ohmyapi/core/logging.py
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
import logging
|
2
|
+
import os
|
3
|
+
import sys
|
4
|
+
|
5
|
+
|
6
|
+
def setup_logging():
|
7
|
+
"""Configure unified logging for ohmyapi + FastAPI/Uvicorn."""
|
8
|
+
log_level = os.getenv("OHMYAPI_LOG_LEVEL", "INFO").upper()
|
9
|
+
level = getattr(logging, log_level, logging.INFO)
|
10
|
+
|
11
|
+
# Root logger (affects FastAPI, uvicorn, etc.)
|
12
|
+
logging.basicConfig(
|
13
|
+
level=level,
|
14
|
+
format="[%(asctime)s] [%(levelname)s] %(name)s: %(message)s",
|
15
|
+
datefmt="%Y-%m-%d %H:%M:%S",
|
16
|
+
handlers=[
|
17
|
+
logging.StreamHandler(sys.stdout)
|
18
|
+
]
|
19
|
+
)
|
20
|
+
|
21
|
+
# Separate ohmyapi logger (optional)
|
22
|
+
logger = logging.getLogger("ohmyapi")
|
23
|
+
|
24
|
+
# Direct warnings/errors to stderr
|
25
|
+
class LevelFilter(logging.Filter):
|
26
|
+
def filter(self, record):
|
27
|
+
# Send warnings+ to stderr, everything else to stdout
|
28
|
+
if record.levelno >= logging.WARNING:
|
29
|
+
record.stream = sys.stderr
|
30
|
+
else:
|
31
|
+
record.stream = sys.stdout
|
32
|
+
return True
|
33
|
+
|
34
|
+
for handler in logger.handlers:
|
35
|
+
handler.addFilter(LevelFilter())
|
36
|
+
|
37
|
+
logger.setLevel(level)
|
38
|
+
return logger
|
ohmyapi/core/runtime.py
CHANGED
@@ -7,7 +7,7 @@ import sys
|
|
7
7
|
from http import HTTPStatus
|
8
8
|
from pathlib import Path
|
9
9
|
from types import ModuleType
|
10
|
-
from typing import Any, Dict, Generator, List, Optional, Type
|
10
|
+
from typing import Any, Dict, Generator, List, Optional, Tuple, Type
|
11
11
|
|
12
12
|
import click
|
13
13
|
from aerich import Command as AerichCommand
|
@@ -15,8 +15,11 @@ from aerich.exceptions import NotInitedError
|
|
15
15
|
from fastapi import APIRouter, FastAPI
|
16
16
|
from tortoise import Tortoise
|
17
17
|
|
18
|
+
from ohmyapi.core.logging import setup_logging
|
18
19
|
from ohmyapi.db.model import Model
|
19
20
|
|
21
|
+
logger = setup_logging()
|
22
|
+
|
20
23
|
|
21
24
|
class Project:
|
22
25
|
"""
|
@@ -29,6 +32,7 @@ class Project:
|
|
29
32
|
"""
|
30
33
|
|
31
34
|
def __init__(self, project_path: str):
|
35
|
+
logger.debug(f"Loading project: {project_path}")
|
32
36
|
self.project_path = Path(project_path).resolve()
|
33
37
|
self._apps: Dict[str, App] = {}
|
34
38
|
self.migrations_dir = self.project_path / "migrations"
|
@@ -88,10 +92,14 @@ class Project:
|
|
88
92
|
|
89
93
|
def configure_app(self, app: FastAPI) -> FastAPI:
|
90
94
|
"""
|
91
|
-
Attach project routes and event handlers to given
|
95
|
+
Attach project middlewares and routes and event handlers to given
|
96
|
+
FastAPI instance.
|
92
97
|
"""
|
93
|
-
|
98
|
+
app.router.prefix = getattr(self.settings, "API_PREFIX", "")
|
99
|
+
# Attach project middlewares and routes.
|
94
100
|
for app_name, app_def in self._apps.items():
|
101
|
+
for middleware, kwargs in app_def.middlewares:
|
102
|
+
app.add_middleware(middleware, **kwargs)
|
95
103
|
for router in app_def.routers:
|
96
104
|
app.include_router(router)
|
97
105
|
|
@@ -229,11 +237,16 @@ class App:
|
|
229
237
|
# Reference to this app's routes modules.
|
230
238
|
self._routers: Dict[str, ModuleType] = {}
|
231
239
|
|
240
|
+
# Reference to this apps middlewares.
|
241
|
+
self._middlewares: List[Tuple[Any, Dict[str, Any]]] = []
|
242
|
+
|
232
243
|
# Import the app, so its __init__.py runs.
|
233
244
|
mod: ModuleType = importlib.import_module(name)
|
234
245
|
|
246
|
+
logger.debug(f"Loading app: {self.name}")
|
235
247
|
self.__load_models(f"{self.name}.models")
|
236
248
|
self.__load_routes(f"{self.name}.routes")
|
249
|
+
self.__load_middlewares(f"{self.name}.middlewares")
|
237
250
|
|
238
251
|
def __repr__(self):
|
239
252
|
return json.dumps(self.dict(), indent=2)
|
@@ -251,7 +264,6 @@ class App:
|
|
251
264
|
try:
|
252
265
|
importlib.import_module(mod_name)
|
253
266
|
except ModuleNotFoundError:
|
254
|
-
print(f"no models detected: {mod_name}")
|
255
267
|
return
|
256
268
|
|
257
269
|
# Acoid duplicates.
|
@@ -269,9 +281,11 @@ class App:
|
|
269
281
|
and issubclass(value, Model)
|
270
282
|
and not name == Model.__name__
|
271
283
|
):
|
284
|
+
# monkey-patch __module__ to point to well-known aliases
|
272
285
|
value.__module__ = value.__module__.replace("ohmyapi.builtin.", "ohmyapi_")
|
273
286
|
if value.__module__.startswith(mod_name):
|
274
287
|
self._models[mod_name] = self._models.get(mod_name, []) + [value]
|
288
|
+
logger.debug(f" - Model: {mod_name} -> {name}")
|
275
289
|
|
276
290
|
# if it's a package, recurse into submodules
|
277
291
|
if hasattr(mod, "__path__"):
|
@@ -293,7 +307,6 @@ class App:
|
|
293
307
|
try:
|
294
308
|
importlib.import_module(mod_name)
|
295
309
|
except ModuleNotFound:
|
296
|
-
print(f"no routes detected: {mod_name}")
|
297
310
|
return
|
298
311
|
|
299
312
|
# Avoid duplicates.
|
@@ -308,6 +321,7 @@ class App:
|
|
308
321
|
for name, value in vars(mod).copy().items():
|
309
322
|
if isinstance(value, APIRouter) and not name == APIRouter.__name__:
|
310
323
|
self._routers[mod_name] = self._routers.get(mod_name, []) + [value]
|
324
|
+
logger.debug(f" - Router: {mod_name} -> {name} -> {value.routes}")
|
311
325
|
|
312
326
|
# if it's a package, recurse into submodules
|
313
327
|
if hasattr(mod, "__path__"):
|
@@ -319,6 +333,17 @@ class App:
|
|
319
333
|
# Walk the walk.
|
320
334
|
walk(mod_name)
|
321
335
|
|
336
|
+
def __load_middlewares(self, mod_name):
|
337
|
+
try:
|
338
|
+
mod = importlib.import_module(mod_name)
|
339
|
+
except ModuleNotFoundError:
|
340
|
+
return
|
341
|
+
|
342
|
+
getter = getattr(mod, "get", None)
|
343
|
+
if getter is not None:
|
344
|
+
for middleware in getter():
|
345
|
+
self._middlewares.append(middleware)
|
346
|
+
|
322
347
|
def __serialize_route(self, route):
|
323
348
|
"""
|
324
349
|
Convert APIRoute to JSON-serializable dict.
|
@@ -332,6 +357,12 @@ class App:
|
|
332
357
|
def __serialize_router(self):
|
333
358
|
return [self.__serialize_route(route) for route in self.routes]
|
334
359
|
|
360
|
+
def __serialize_middleware(self):
|
361
|
+
out = []
|
362
|
+
for m in self.middlewares:
|
363
|
+
out.append((m[0].__name__, m[1]))
|
364
|
+
return out
|
365
|
+
|
335
366
|
@property
|
336
367
|
def models(self) -> List[ModuleType]:
|
337
368
|
"""
|
@@ -363,6 +394,11 @@ class App:
|
|
363
394
|
out.extend(r.routes)
|
364
395
|
return out
|
365
396
|
|
397
|
+
@property
|
398
|
+
def middlewares(self):
|
399
|
+
"""Returns the list of this app's middlewares."""
|
400
|
+
return self._middlewares
|
401
|
+
|
366
402
|
def dict(self) -> Dict[str, Any]:
|
367
403
|
"""
|
368
404
|
Convenience method for serializing the runtime data.
|
@@ -371,5 +407,6 @@ class App:
|
|
371
407
|
"models": [
|
372
408
|
f"{self.name}.{m.__name__}" for m in self.models[f"{self.name}.models"]
|
373
409
|
],
|
410
|
+
"middlewares": self.__serialize_middleware(),
|
374
411
|
"routes": self.__serialize_router(),
|
375
412
|
}
|
ohmyapi/core/scaffolding.py
CHANGED
@@ -2,12 +2,16 @@ from pathlib import Path
|
|
2
2
|
|
3
3
|
from jinja2 import Environment, FileSystemLoader
|
4
4
|
|
5
|
+
from ohmyapi.core.logging import setup_logging
|
6
|
+
|
5
7
|
import shutil
|
6
8
|
|
7
9
|
# Base templates directory
|
8
10
|
TEMPLATE_DIR = Path(__file__).parent / "templates"
|
9
11
|
env = Environment(loader=FileSystemLoader(str(TEMPLATE_DIR)))
|
10
12
|
|
13
|
+
logger = setup_logging()
|
14
|
+
|
11
15
|
|
12
16
|
def render_template_file(template_path: Path, context: dict, output_path: Path):
|
13
17
|
"""Render a single Jinja2 template file to disk."""
|
@@ -60,7 +64,7 @@ def copy_static(dir_name: str, target_dir: Path):
|
|
60
64
|
template_dir = TEMPLATE_DIR / dir_name
|
61
65
|
target_dir = Path(target_dir)
|
62
66
|
if not template_dir.exists():
|
63
|
-
|
67
|
+
logger.error(f"no templates found under: {dir_name}")
|
64
68
|
return
|
65
69
|
|
66
70
|
for root, _, files in template_dir.walk():
|
@@ -69,19 +73,15 @@ def copy_static(dir_name: str, target_dir: Path):
|
|
69
73
|
src = root_path / file
|
70
74
|
dst = target_dir / file
|
71
75
|
if dst.exists():
|
72
|
-
|
76
|
+
logger.warning(f"⛔ File exists, skipping: {dst}")
|
73
77
|
continue
|
74
|
-
shutil.copy(src,
|
75
|
-
print(f"✅ Templates created successfully.")
|
76
|
-
print(f"🔧 Next, run `docker compose up -d --build`")
|
78
|
+
shutil.copy(src, dst)
|
77
79
|
|
78
80
|
def startproject(name: str):
|
79
81
|
"""Create a new project: flat structure, all project templates go into <name>/"""
|
80
82
|
target_dir = Path(name).resolve()
|
81
83
|
target_dir.mkdir(exist_ok=True)
|
82
84
|
render_template_dir("project", target_dir, {"project_name": name})
|
83
|
-
print(f"✅ Project '{name}' created successfully.")
|
84
|
-
print(f"🔧 Next, configure your project in {target_dir / 'settings.py'}")
|
85
85
|
|
86
86
|
|
87
87
|
def startapp(name: str, project: str):
|
@@ -94,5 +94,3 @@ def startapp(name: str, project: str):
|
|
94
94
|
{"project_name": target_dir.resolve().name, "app_name": name},
|
95
95
|
subdir_name=name,
|
96
96
|
)
|
97
|
-
print(f"✅ App '{name}' created in project '{target_dir}' successfully.")
|
98
|
-
print(f"🔧 Remember to add '{name}' to your INSTALLED_APPS!")
|
ohmyapi/db/__init__.py
CHANGED
@@ -0,0 +1,26 @@
|
|
1
|
+
from fastapi import FastAPI
|
2
|
+
from fastapi.middleware.cors import CORSMiddleware
|
3
|
+
|
4
|
+
from typing import Any, Dict, List, Tuple
|
5
|
+
|
6
|
+
import settings
|
7
|
+
|
8
|
+
DEFAULT_ORIGINS = ["http://localhost", "http://localhost:8000"]
|
9
|
+
DEFAULT_CREDENTIALS = False
|
10
|
+
DEFAULT_METHODS = ["*"]
|
11
|
+
DEFAULT_HEADERS = ["*"]
|
12
|
+
|
13
|
+
CORS_CONFIG: Dict[str, Any] = getattr(settings, "MIDDLEWARE_CORS", {})
|
14
|
+
|
15
|
+
if not isinstance(CORS_CONFIG, dict):
|
16
|
+
raise ValueError("MIDDLEWARE_CORS must be of type dict")
|
17
|
+
|
18
|
+
middleware = [
|
19
|
+
(CORSMiddleware, {
|
20
|
+
"allow_origins": CORS_CONFIG.get("ALLOW_ORIGINS", DEFAULT_ORIGINS),
|
21
|
+
"allow_credentials": CORS_CONFIG.get("ALLOW_CREDENTIALS", DEFAULT_CREDENTIALS),
|
22
|
+
"allow_methods": CORS_CONFIG.get("ALLOW_METHODS", DEFAULT_METHODS),
|
23
|
+
"allow_headers": CORS_CONFIG.get("ALLOW_HEADERS", DEFAULT_HEADERS),
|
24
|
+
}),
|
25
|
+
]
|
26
|
+
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: ohmyapi
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.4.0
|
4
4
|
Summary: Django-flavored scaffolding and management layer around FastAPI, Pydantic, TortoiseORM and Aerich migrations
|
5
5
|
License-Expression: MIT
|
6
6
|
Keywords: fastapi,tortoise,orm,pydantic,async,web-framework
|
@@ -35,9 +35,10 @@ Description-Content-Type: text/markdown
|
|
35
35
|
|
36
36
|
# OhMyAPI
|
37
37
|
|
38
|
-
OhMyAPI is a web-application scaffolding framework and management layer built around `FastAPI
|
38
|
+
OhMyAPI is a web-application scaffolding framework and management layer built around `FastAPI`.
|
39
|
+
It is a thin layer that tightly integrates TortoiseORM and Aerich migrations.
|
39
40
|
|
40
|
-
> *Think: Django RestFramework
|
41
|
+
> *Think: *"Django RestFramework"*, but less clunky and instead 100% async.*
|
41
42
|
|
42
43
|
It is ***blazingly fast***, extremely ***fun to use*** and comes with ***batteries included***!
|
43
44
|
|
@@ -47,7 +48,7 @@ It is ***blazingly fast***, extremely ***fun to use*** and comes with ***batteri
|
|
47
48
|
- Django-like per-app migrations (makemigrations & migrate) via Aerich
|
48
49
|
- Django-like CLI tooling (startproject, startapp, shell, serve, etc)
|
49
50
|
- Customizable pydantic model serializer built-in
|
50
|
-
- Various optional built-in apps you can hook into your project
|
51
|
+
- Various optional built-in apps you can hook into your project (i.e. authentication and more)
|
51
52
|
- Highly configurable and customizable
|
52
53
|
- 100% async
|
53
54
|
|
@@ -1,30 +1,32 @@
|
|
1
|
-
ohmyapi/__init__.py,sha256=
|
1
|
+
ohmyapi/__init__.py,sha256=2ohBfKaesG66_1MkCKfwltW-m1FPoCxzeDJDsA74KDc,22
|
2
2
|
ohmyapi/__main__.py,sha256=wcCrL4PjG51r5wVKqJhcoJPTLfHW0wNbD31DrUN0MWI,28
|
3
3
|
ohmyapi/builtin/auth/__init__.py,sha256=vOVCSJX8BALzs8h5ZW9507bjoscP37bncMjdMmBXcMM,42
|
4
|
-
ohmyapi/builtin/auth/models.py,sha256=
|
4
|
+
ohmyapi/builtin/auth/models.py,sha256=3T6q0K3S9U__zif784oSb44bWqt3oCTpgPJlEK1YPFk,2598
|
5
5
|
ohmyapi/builtin/auth/permissions.py,sha256=mxsnhF_UGesTFle7v1JHORkNODtQ0qanAL3FtOcMCEY,145
|
6
6
|
ohmyapi/builtin/auth/routes.py,sha256=r887BWea20vinX88QBuxGzQc9BrIi-LqyStM0V1cl1o,6304
|
7
7
|
ohmyapi/builtin/demo/__init__.py,sha256=44Yo3mYmlKSKEwVp6O9urr-C_3qDQzCYLMn6B9i6wew,29
|
8
8
|
ohmyapi/builtin/demo/models.py,sha256=r06rfuhPJaI2fYsQ24L1JCOd67f2GQNfGnkgKAptOX8,1404
|
9
9
|
ohmyapi/builtin/demo/routes.py,sha256=1VTlEttrez6Qnhrz_9sTA-emtfXem0s0BkPVcLvg3k0,1801
|
10
|
-
ohmyapi/cli.py,sha256=
|
10
|
+
ohmyapi/cli.py,sha256=iWP0vPkodC_Y3MenNs9AH8FR_d-XpoXliGtz4inWkR4,5114
|
11
11
|
ohmyapi/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
12
|
-
ohmyapi/core/
|
13
|
-
ohmyapi/core/
|
12
|
+
ohmyapi/core/logging.py,sha256=YHN-I2--WP1uXy4W3Q1aD1XLiG9-CrVWvyCYe8HsD9E,1094
|
13
|
+
ohmyapi/core/runtime.py,sha256=NxTMMo4mHJlUxhDZWTxzYvTWkQKEuTrgVuZ-g6mHabg,13681
|
14
|
+
ohmyapi/core/scaffolding.py,sha256=EehJesfDBB8vcMvc_T351QYdyJuyMGBA__BpLT33oVA,3122
|
14
15
|
ohmyapi/core/templates/app/__init__.py.j2,sha256=QwVIQVUGZVhdH1d4NrvL7NTsK4-T4cihzYs8UVX2dt4,43
|
15
16
|
ohmyapi/core/templates/app/models.py.j2,sha256=_3w-vFJ5fgsmncsCv34k_wyCMF78jufbSSglns4gbb0,119
|
16
17
|
ohmyapi/core/templates/app/routes.py.j2,sha256=SxLz_wvakusj5txbXToZyNq6ZmcamSwNjVrI0MNPRl0,1037
|
17
|
-
ohmyapi/core/templates/docker/Dockerfile,sha256=
|
18
|
-
ohmyapi/core/templates/docker/docker-compose.yml,sha256=
|
18
|
+
ohmyapi/core/templates/docker/Dockerfile,sha256=KnfCscQYf1VQHrn_LVs1sNlcbsslHoRKbgMeGjuwD7k,580
|
19
|
+
ohmyapi/core/templates/docker/docker-compose.yml,sha256=GnFZN8hrxrIdpqQ2COMmBoFcfeiUiWtI4fm58KvjQOY,317
|
19
20
|
ohmyapi/core/templates/project/README.md.j2,sha256=SjR4JIrg-8XRE-UntUDwiw8jDpYitD_UjwoKkYJ7GLw,22
|
20
|
-
ohmyapi/core/templates/project/pyproject.toml.j2,sha256=
|
21
|
+
ohmyapi/core/templates/project/pyproject.toml.j2,sha256=xXMlZOZ4ewyE7Xlvk8kwyF1781-AZfG_CuNtXQGfxqk,522
|
21
22
|
ohmyapi/core/templates/project/settings.py.j2,sha256=So6w1OiL_jU-FyeT8IHueDjGNuEoSkYhabhHpne2fUU,140
|
22
|
-
ohmyapi/db/__init__.py,sha256=
|
23
|
+
ohmyapi/db/__init__.py,sha256=qoZZvji8wLwU38DSw9IcQOZm0C2sMF-fGn-b1J1fzws,239
|
23
24
|
ohmyapi/db/exceptions.py,sha256=vb4IIUoeYAY6sK42zRtjMy-39IFVi_Qb6mWySTY0jYw,34
|
24
25
|
ohmyapi/db/model/__init__.py,sha256=k3StTNuKatpwZo_Z5JBFa-927eJrzibFE8U4SA82asc,32
|
25
26
|
ohmyapi/db/model/model.py,sha256=ui4g78c5xoS06Dj8Cdk7QgTjRnE68zKeL-AdmeYYPuQ,2776
|
27
|
+
ohmyapi/middleware/cors.py,sha256=p8HlKsZLM6jKLEa2-q22cIEZUKawirMQB9ajfxaM4xw,818
|
26
28
|
ohmyapi/router.py,sha256=5g0U59glu4hxxnIoTSFzb2S2offkOT3eE39aprzVxwo,83
|
27
|
-
ohmyapi-0.
|
28
|
-
ohmyapi-0.
|
29
|
-
ohmyapi-0.
|
30
|
-
ohmyapi-0.
|
29
|
+
ohmyapi-0.4.0.dist-info/METADATA,sha256=xwYfCORH7xurtw3IfqpXQEdLP9hLw1MKrHQ4wdkH3SY,2657
|
30
|
+
ohmyapi-0.4.0.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
|
31
|
+
ohmyapi-0.4.0.dist-info/entry_points.txt,sha256=wb3lw8-meAlpiv1mqcQ3m25ukL7djagU_w89GkrC37k,43
|
32
|
+
ohmyapi-0.4.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|