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 CHANGED
@@ -1 +1 @@
1
- __VERSION__ = "0.3.2"
1
+ __VERSION__ = "0.4.0"
@@ -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, pre_delete, pre_save
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 = ("password_hash",)
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 authenticate(cls, username: str, password: str) -> Optional["User"]:
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 = project.configure_app(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 directly available
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
- email=email, username=username, is_staff=True, is_admin=True
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())
@@ -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 FastAPI instance.
95
+ Attach project middlewares and routes and event handlers to given
96
+ FastAPI instance.
92
97
  """
93
- # Attach project routes.
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
  }
@@ -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
- print(f"no templates found under: {dir_name}")
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
- print(f"⛔ File exists, skipping: {dst}")
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!")
@@ -21,7 +21,6 @@ ENV PATH="$POETRY_HOME/bin:$PATH"
21
21
 
22
22
  WORKDIR /app
23
23
  COPY pyproject.toml poetry.lock* /app/
24
- RUN poetry lock
25
24
  RUN poetry install
26
25
  COPY . /app
27
26
 
@@ -1,4 +1,14 @@
1
1
  services:
2
+ # db:
3
+ # image: postgres:latest
4
+ # restart: unless-stopped
5
+ # environment:
6
+ # POSTGRES_DB: ohmyapi
7
+ # POSTGRES_USER: ohmyapi
8
+ # POSTGRES_PASSWORD: ohmyapi
9
+ # ports:
10
+ # - 5432:5432
11
+
2
12
  app:
3
13
  build:
4
14
  context: .
@@ -10,7 +10,7 @@ readme = "README.md"
10
10
  license = { text = "MIT" }
11
11
 
12
12
  dependencies = [
13
- "ohmyapi"
13
+ # "asyncpg"
14
14
  ]
15
15
 
16
16
  [tool.poetry.group.dev.dependencies]
ohmyapi/db/__init__.py CHANGED
@@ -1,3 +1,4 @@
1
+ from tortoise.expressions import Q
1
2
  from tortoise.manager import Manager
2
3
  from tortoise.queryset import QuerySet
3
4
  from tortoise.signals import (
@@ -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.2
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`, `TortoiseORM` and `Aerich` migrations.
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, but less clunky and 100% async.*
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=2oHRA-Di_T9uJrAqGarogp-SaY0SBCIWtlLOxTyTviE,22
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=8az4TKdC6PUha3_HyslKinDT2-yAN-aBoYUQcjxm7Js,1728
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=gHiPWN3pkrrIJ69WFBHt0BTRNVI-YSJD7VhtqQRwKuc,4447
10
+ ohmyapi/cli.py,sha256=iWP0vPkodC_Y3MenNs9AH8FR_d-XpoXliGtz4inWkR4,5114
11
11
  ohmyapi/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
- ohmyapi/core/runtime.py,sha256=pNfFRk7sFX8FePX6cnb4H7kLh5CauNnA_gmc0rsWIh8,12287
13
- ohmyapi/core/scaffolding.py,sha256=1KF67aBvd08xjDhIXhI-nDhClJAw2TB3tYVLiMmeYOk,3426
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=4-QgqK6h9sR7hYKGUvRife6GpLTm7EOzYupr54B3qUY,596
18
- ohmyapi/core/templates/docker/docker-compose.yml,sha256=Jr344DALp28_w9d9O-HIuQgwaacIHabyFyvBuOQ-PeQ,102
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=D3d5yv2317owH3tFYN6klMavt74gTqqhDsHe0jhqUeA,521
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=5QKUycxnN83DOUD_Etoee9tEOYjnZ74deqrSOOx_MiQ,204
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.3.2.dist-info/METADATA,sha256=jN-z3UGK2i-h4f4jbRr_buU8FfPkTKxKr3W9fSrxQ0E,2575
28
- ohmyapi-0.3.2.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
29
- ohmyapi-0.3.2.dist-info/entry_points.txt,sha256=wb3lw8-meAlpiv1mqcQ3m25ukL7djagU_w89GkrC37k,43
30
- ohmyapi-0.3.2.dist-info/RECORD,,
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,,