hypern 0.3.6__cp310-cp310-win32.whl → 0.3.8__cp310-cp310-win32.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.
- hypern/__init__.py +21 -1
- hypern/application.py +26 -36
- hypern/args_parser.py +0 -26
- hypern/database/sql/__init__.py +24 -1
- hypern/database/sql/field.py +130 -491
- hypern/database/sql/migrate.py +263 -0
- hypern/database/sql/model.py +4 -3
- hypern/database/sql/query.py +2 -2
- hypern/datastructures.py +2 -2
- hypern/hypern.cp310-win32.pyd +0 -0
- hypern/hypern.pyi +4 -9
- hypern/openapi/schemas.py +5 -7
- hypern/routing/route.py +8 -12
- hypern/worker.py +265 -21
- {hypern-0.3.6.dist-info → hypern-0.3.8.dist-info}/METADATA +16 -6
- {hypern-0.3.6.dist-info → hypern-0.3.8.dist-info}/RECORD +18 -18
- {hypern-0.3.6.dist-info → hypern-0.3.8.dist-info}/WHEEL +1 -1
- hypern/ws.py +0 -16
- {hypern-0.3.6.dist-info → hypern-0.3.8.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,263 @@
|
|
1
|
+
# import os
|
2
|
+
# import sys
|
3
|
+
# import inspect
|
4
|
+
# import importlib
|
5
|
+
# import hashlib
|
6
|
+
# import argparse
|
7
|
+
# from datetime import datetime
|
8
|
+
# from typing import List, Type, Dict
|
9
|
+
|
10
|
+
# from hypern.config import get_config
|
11
|
+
# from .model import Model
|
12
|
+
|
13
|
+
|
14
|
+
# class MigrationManager:
|
15
|
+
# """Manages database migrations and schema changes."""
|
16
|
+
|
17
|
+
# def __init__(self, migrations_dir: str = "migrations"):
|
18
|
+
# self.migrations_dir = migrations_dir
|
19
|
+
# self.config = get_config()
|
20
|
+
# self.ensure_migrations_dir()
|
21
|
+
|
22
|
+
# def ensure_migrations_dir(self):
|
23
|
+
# """Ensure migrations directory exists."""
|
24
|
+
# if not os.path.exists(self.migrations_dir):
|
25
|
+
# os.makedirs(self.migrations_dir)
|
26
|
+
# # Create __init__.py to make it a package
|
27
|
+
# with open(os.path.join(self.migrations_dir, "__init__.py"), "w") as f:
|
28
|
+
# pass
|
29
|
+
|
30
|
+
# def collect_models(self) -> Dict[str, Type[Model]]:
|
31
|
+
# """Collect all model classes from the project."""
|
32
|
+
# models = {}
|
33
|
+
# # Scan all Python files in the project directory
|
34
|
+
# for root, _, files in os.walk("."):
|
35
|
+
# if "venv" in root or "migrations" in root:
|
36
|
+
# continue
|
37
|
+
# for file in files:
|
38
|
+
# if file.endswith(".py"):
|
39
|
+
# module_path = os.path.join(root, file)
|
40
|
+
# module_name = module_path.replace("/", ".").replace("\\", ".")[2:-3]
|
41
|
+
# try:
|
42
|
+
# module = importlib.import_module(module_name)
|
43
|
+
# for name, obj in inspect.getmembers(module):
|
44
|
+
# if inspect.isclass(obj) and issubclass(obj, Model) and obj != Model:
|
45
|
+
# models[obj.__name__] = obj
|
46
|
+
# except (ImportError, AttributeError):
|
47
|
+
# continue
|
48
|
+
# return models
|
49
|
+
|
50
|
+
# def generate_migration(self, name: str):
|
51
|
+
# """Generate a new migration file."""
|
52
|
+
# timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
53
|
+
# migration_id = f"{timestamp}_{name}"
|
54
|
+
# filename = f"{migration_id}.py"
|
55
|
+
# filepath = os.path.join(self.migrations_dir, filename)
|
56
|
+
|
57
|
+
# models = self.collect_models()
|
58
|
+
|
59
|
+
# # Generate migration content
|
60
|
+
# content = self._generate_migration_content(migration_id, models)
|
61
|
+
|
62
|
+
# with open(filepath, "w") as f:
|
63
|
+
# f.write(content)
|
64
|
+
|
65
|
+
# print(f"Created migration: {filename}")
|
66
|
+
|
67
|
+
# def _generate_migration_content(self, migration_id: str, models: Dict[str, Type[Model]]) -> str:
|
68
|
+
# """Generate the content for a migration file."""
|
69
|
+
# content = [
|
70
|
+
# "from typing import List",
|
71
|
+
# "from hypern.migrations import Migration\n",
|
72
|
+
# ]
|
73
|
+
|
74
|
+
# # Import all models
|
75
|
+
# for model_name in models.keys():
|
76
|
+
# content.append(f"from app.models import {model_name}")
|
77
|
+
|
78
|
+
# content.extend([
|
79
|
+
# "\n\nclass " + migration_id + "(Migration):",
|
80
|
+
# " \"\"\"",
|
81
|
+
# " Auto-generated migration.",
|
82
|
+
# " \"\"\"",
|
83
|
+
# "",
|
84
|
+
# " def up(self) -> List[str]:",
|
85
|
+
# " return [",
|
86
|
+
# ])
|
87
|
+
|
88
|
+
# # Add CREATE TABLE statements
|
89
|
+
# for model in models.values():
|
90
|
+
# content.append(f" '''{model.create_table_sql()}''',")
|
91
|
+
|
92
|
+
# content.extend([
|
93
|
+
# " ]",
|
94
|
+
# "",
|
95
|
+
# " def down(self) -> List[str]:",
|
96
|
+
# " return [",
|
97
|
+
# ])
|
98
|
+
|
99
|
+
# # Add DROP TABLE statements in reverse order
|
100
|
+
# for model_name in reversed(list(models.keys())):
|
101
|
+
# content.append(f" '''DROP TABLE IF EXISTS {model_name.lower()} CASCADE;''',")
|
102
|
+
|
103
|
+
# content.extend([
|
104
|
+
# " ]",
|
105
|
+
# ""
|
106
|
+
# ])
|
107
|
+
|
108
|
+
# return "\n".join(content)
|
109
|
+
|
110
|
+
# def get_applied_migrations(self) -> List[str]:
|
111
|
+
# """Get list of applied migrations from database."""
|
112
|
+
# session = get_session_database()
|
113
|
+
# try:
|
114
|
+
# result = session.execute("""
|
115
|
+
# SELECT migration_id FROM migrations
|
116
|
+
# ORDER BY applied_at;
|
117
|
+
# """)
|
118
|
+
# return [row[0] for row in result]
|
119
|
+
# except Exception:
|
120
|
+
# # Migrations table doesn't exist yet
|
121
|
+
# return []
|
122
|
+
|
123
|
+
# def apply_migrations(self, target: str = None):
|
124
|
+
# """Apply pending migrations up to target (or all if target is None)."""
|
125
|
+
# # Create migrations table if it doesn't exist
|
126
|
+
# self._ensure_migrations_table()
|
127
|
+
|
128
|
+
# # Get applied and available migrations
|
129
|
+
# applied = set(self.get_applied_migrations())
|
130
|
+
# available = self._get_available_migrations()
|
131
|
+
|
132
|
+
# # Determine which migrations to apply
|
133
|
+
# to_apply = []
|
134
|
+
# for migration_id, module in available.items():
|
135
|
+
# if migration_id not in applied:
|
136
|
+
# to_apply.append((migration_id, module))
|
137
|
+
|
138
|
+
# if target and migration_id == target:
|
139
|
+
# break
|
140
|
+
|
141
|
+
# # Apply migrations
|
142
|
+
# session = get_session_database()
|
143
|
+
# for migration_id, module in to_apply:
|
144
|
+
# print(f"Applying migration: {migration_id}")
|
145
|
+
|
146
|
+
# migration = module()
|
147
|
+
# for sql in migration.up():
|
148
|
+
# session.execute(sql)
|
149
|
+
|
150
|
+
# # Record migration
|
151
|
+
# session.execute(
|
152
|
+
# "INSERT INTO migrations (migration_id, applied_at) VALUES (%s, NOW())",
|
153
|
+
# (migration_id,)
|
154
|
+
# )
|
155
|
+
# session.commit()
|
156
|
+
|
157
|
+
# def rollback_migrations(self, target: str = None):
|
158
|
+
# """Rollback migrations up to target (or last one if target is None)."""
|
159
|
+
# applied = self.get_applied_migrations()
|
160
|
+
# available = self._get_available_migrations()
|
161
|
+
|
162
|
+
# # Determine which migrations to rollback
|
163
|
+
# to_rollback = []
|
164
|
+
# rollback_all = target == "zero"
|
165
|
+
|
166
|
+
# for migration_id in reversed(applied):
|
167
|
+
# to_rollback.append((migration_id, available[migration_id]))
|
168
|
+
|
169
|
+
# if not rollback_all and (target == migration_id or target is None):
|
170
|
+
# break
|
171
|
+
|
172
|
+
# # Rollback migrations
|
173
|
+
# session = get_session_database()
|
174
|
+
# for migration_id, module in to_rollback:
|
175
|
+
# print(f"Rolling back migration: {migration_id}")
|
176
|
+
|
177
|
+
# migration = module()
|
178
|
+
# for sql in migration.down():
|
179
|
+
# session.execute(sql)
|
180
|
+
|
181
|
+
# # Remove migration record
|
182
|
+
# session.execute(
|
183
|
+
# "DELETE FROM migrations WHERE migration_id = %s",
|
184
|
+
# (migration_id,)
|
185
|
+
# )
|
186
|
+
# session.commit()
|
187
|
+
|
188
|
+
# def _ensure_migrations_table(self):
|
189
|
+
# """Ensure migrations table exists."""
|
190
|
+
# session = get_session_database()
|
191
|
+
# session.execute("""
|
192
|
+
# CREATE TABLE IF NOT EXISTS migrations (
|
193
|
+
# migration_id VARCHAR(255) PRIMARY KEY,
|
194
|
+
# applied_at TIMESTAMP NOT NULL
|
195
|
+
# );
|
196
|
+
# """)
|
197
|
+
# session.commit()
|
198
|
+
|
199
|
+
# def _get_available_migrations(self) -> Dict[str, Type['Migration']]:
|
200
|
+
# """Get available migrations from migrations directory."""
|
201
|
+
# migrations = {}
|
202
|
+
|
203
|
+
# for filename in sorted(os.listdir(self.migrations_dir)):
|
204
|
+
# if filename.endswith(".py") and not filename.startswith("__"):
|
205
|
+
# migration_id = filename[:-3]
|
206
|
+
# module_name = f"{self.migrations_dir}.{migration_id}"
|
207
|
+
# module = importlib.import_module(module_name)
|
208
|
+
|
209
|
+
# for name, obj in inspect.getmembers(module):
|
210
|
+
# if (inspect.isclass(obj) and
|
211
|
+
# name == migration_id and
|
212
|
+
# hasattr(obj, 'up') and
|
213
|
+
# hasattr(obj, 'down')):
|
214
|
+
# migrations[migration_id] = obj
|
215
|
+
|
216
|
+
# return migrations
|
217
|
+
|
218
|
+
|
219
|
+
# class Migration:
|
220
|
+
# """Base class for database migrations."""
|
221
|
+
|
222
|
+
# def up(self) -> List[str]:
|
223
|
+
# """Return list of SQL statements to apply migration."""
|
224
|
+
# raise NotImplementedError
|
225
|
+
|
226
|
+
# def down(self) -> List[str]:
|
227
|
+
# """Return list of SQL statements to rollback migration."""
|
228
|
+
# raise NotImplementedError
|
229
|
+
|
230
|
+
|
231
|
+
# def main():
|
232
|
+
# parser = argparse.ArgumentParser(description="Database migration tool")
|
233
|
+
|
234
|
+
# subparsers = parser.add_subparsers(dest="command", help="Commands")
|
235
|
+
|
236
|
+
# # makemigrations command
|
237
|
+
# make_parser = subparsers.add_parser("makemigrations", help="Generate new migration")
|
238
|
+
# make_parser.add_argument("name", help="Migration name")
|
239
|
+
|
240
|
+
# # migrate command
|
241
|
+
# migrate_parser = subparsers.add_parser("migrate", help="Apply migrations")
|
242
|
+
# migrate_parser.add_argument("--target", help="Target migration (default: latest)")
|
243
|
+
|
244
|
+
# # rollback command
|
245
|
+
# rollback_parser = subparsers.add_parser("rollback", help="Rollback migrations")
|
246
|
+
# rollback_parser.add_argument("--target", help="Target migration (default: last applied)")
|
247
|
+
|
248
|
+
# args = parser.parse_args()
|
249
|
+
|
250
|
+
# manager = MigrationManager()
|
251
|
+
|
252
|
+
# if args.command == "makemigrations":
|
253
|
+
# manager.generate_migration(args.name)
|
254
|
+
# elif args.command == "migrate":
|
255
|
+
# manager.apply_migrations(args.target)
|
256
|
+
# elif args.command == "rollback":
|
257
|
+
# manager.rollback_migrations(args.target)
|
258
|
+
# else:
|
259
|
+
# parser.print_help()
|
260
|
+
|
261
|
+
|
262
|
+
# if __name__ == "__main__":
|
263
|
+
# main()
|
hypern/database/sql/model.py
CHANGED
@@ -5,7 +5,7 @@ from hypern.config import context_store
|
|
5
5
|
from hypern.exceptions import OutOfScopeApplicationException
|
6
6
|
from hypern.hypern import get_session_database
|
7
7
|
|
8
|
-
from .field import Field,
|
8
|
+
from .field import Field, ForeignKeyField
|
9
9
|
from .query import QuerySet
|
10
10
|
|
11
11
|
|
@@ -77,7 +77,7 @@ class Model(metaclass=MetaModel):
|
|
77
77
|
fields_sql.append(cls._get_field_sql(name, field))
|
78
78
|
if field.index:
|
79
79
|
indexes_sql.append(cls._get_index_sql(name))
|
80
|
-
if isinstance(field,
|
80
|
+
if isinstance(field, ForeignKeyField):
|
81
81
|
foreign_keys.append(cls._get_foreign_key_sql(name, field))
|
82
82
|
|
83
83
|
fields_sql.extend(foreign_keys)
|
@@ -109,7 +109,8 @@ class Model(metaclass=MetaModel):
|
|
109
109
|
|
110
110
|
@classmethod
|
111
111
|
def _get_foreign_key_sql(cls, name, field) -> str:
|
112
|
-
|
112
|
+
target_table = field.to_model.__name__.lower() if not isinstance(field.to_model, str) else field.to_model.lower()
|
113
|
+
return f"FOREIGN KEY ({name}) REFERENCES {target_table}({field.related_field}) ON DELETE {field.on_delete} ON UPDATE {field.on_update}"
|
113
114
|
|
114
115
|
def save(self):
|
115
116
|
query_object = QuerySet(self)
|
hypern/database/sql/query.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
from enum import Enum
|
2
2
|
from typing import Any, Dict, List, Tuple, Union
|
3
|
-
from hypern.database.sql.field import
|
3
|
+
from hypern.database.sql.field import ForeignKeyField
|
4
4
|
|
5
5
|
|
6
6
|
class JoinType(Enum):
|
@@ -553,7 +553,7 @@ class QuerySet:
|
|
553
553
|
"""
|
554
554
|
qs = self.clone()
|
555
555
|
for field in fields:
|
556
|
-
if field in qs.model._fields and isinstance(qs.model._fields[field],
|
556
|
+
if field in qs.model._fields and isinstance(qs.model._fields[field], ForeignKeyField):
|
557
557
|
qs._selected_related.add(field)
|
558
558
|
return qs
|
559
559
|
|
hypern/datastructures.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
from typing import Optional
|
2
2
|
from enum import Enum
|
3
|
-
from pydantic import BaseModel, AnyUrl
|
3
|
+
from pydantic import BaseModel, AnyUrl
|
4
4
|
|
5
5
|
|
6
6
|
class BaseModelWithConfig(BaseModel):
|
@@ -10,7 +10,7 @@ class BaseModelWithConfig(BaseModel):
|
|
10
10
|
class Contact(BaseModelWithConfig):
|
11
11
|
name: Optional[str] = None
|
12
12
|
url: Optional[AnyUrl] = None
|
13
|
-
email: Optional[
|
13
|
+
email: Optional[str] = None
|
14
14
|
|
15
15
|
|
16
16
|
class License(BaseModelWithConfig):
|
hypern/hypern.cp310-win32.pyd
CHANGED
Binary file
|
hypern/hypern.pyi
CHANGED
@@ -7,7 +7,7 @@ from enum import Enum
|
|
7
7
|
@dataclass
|
8
8
|
class BaseSchemaGenerator:
|
9
9
|
remove_converter: Callable[[str], str]
|
10
|
-
parse_docstring: Callable[
|
10
|
+
parse_docstring: Callable[..., str]
|
11
11
|
|
12
12
|
@dataclass
|
13
13
|
class SwaggerUI:
|
@@ -181,21 +181,19 @@ class Server:
|
|
181
181
|
def set_router(self, router: Router) -> None: ...
|
182
182
|
def set_websocket_router(self, websocket_router: WebsocketRouter) -> None: ...
|
183
183
|
def start(self, socket: SocketHeld, worker: int, max_blocking_threads: int) -> None: ...
|
184
|
-
def inject(self, key: str, value: Any) -> None: ...
|
185
|
-
def set_injected(self, injected: Dict[str, Any]) -> None: ...
|
186
184
|
def set_before_hooks(self, hooks: List[FunctionInfo]) -> None: ...
|
187
185
|
def set_after_hooks(self, hooks: List[FunctionInfo]) -> None: ...
|
188
186
|
def set_response_headers(self, headers: Dict[str, str]) -> None: ...
|
189
187
|
def set_startup_handler(self, on_startup: FunctionInfo) -> None: ...
|
190
188
|
def set_shutdown_handler(self, on_shutdown: FunctionInfo) -> None: ...
|
191
|
-
def set_auto_compression(self, enabled: bool) -> None: ...
|
192
189
|
def set_database_config(self, config: DatabaseConfig) -> None: ...
|
193
|
-
def
|
190
|
+
def set_dependencies(self, dependencies: Dict[str, Any]) -> None: ...
|
194
191
|
|
195
192
|
class Route:
|
196
193
|
path: str
|
197
194
|
function: FunctionInfo
|
198
195
|
method: str
|
196
|
+
doc: str | None = None
|
199
197
|
|
200
198
|
def matches(self, path: str, method: str) -> str: ...
|
201
199
|
def clone_route(self) -> Route: ...
|
@@ -277,7 +275,7 @@ class UploadedFile:
|
|
277
275
|
path: str
|
278
276
|
size: int
|
279
277
|
content: bytes
|
280
|
-
|
278
|
+
file_name: str
|
281
279
|
|
282
280
|
@dataclass
|
283
281
|
class BodyData:
|
@@ -330,8 +328,5 @@ class DatabaseTransaction:
|
|
330
328
|
def bulk_change(self, query: str, params: List[List[Any]], batch_size: int) -> int | None: ...
|
331
329
|
def commit(self) -> None: ...
|
332
330
|
def rollback(self) -> None: ...
|
333
|
-
def __del__(self) -> None: ...
|
334
|
-
def __enter__(self) -> None: ...
|
335
|
-
def __exit__(self, _exc_type, _exc_value, _traceback) -> None: ...
|
336
331
|
|
337
332
|
def get_session_database(context_id: str) -> DatabaseTransaction: ...
|
hypern/openapi/schemas.py
CHANGED
@@ -37,17 +37,15 @@ class SchemaGenerator(BaseSchemaGenerator):
|
|
37
37
|
def get_schema(self, app) -> dict[str, typing.Any]:
|
38
38
|
schema = dict(self.base_schema)
|
39
39
|
schema.setdefault("paths", {})
|
40
|
-
|
41
|
-
|
42
|
-
for endpoint in endpoints_info:
|
43
|
-
parsed = self.parse_docstring(endpoint.func)
|
40
|
+
for route in app.router.routes:
|
41
|
+
parsed = self.parse_docstring(route.doc)
|
44
42
|
|
45
43
|
if not parsed:
|
46
44
|
continue
|
47
45
|
|
48
|
-
if
|
49
|
-
schema["paths"][
|
46
|
+
if route.path not in schema["paths"]:
|
47
|
+
schema["paths"][route.path] = {}
|
50
48
|
|
51
|
-
schema["paths"][
|
49
|
+
schema["paths"][route.path][route.method.lower()] = orjson.loads(parsed)
|
52
50
|
|
53
51
|
return schema
|
hypern/routing/route.py
CHANGED
@@ -224,8 +224,8 @@ class Route:
|
|
224
224
|
raise ValueError(f"No handler found for route: {self.path}")
|
225
225
|
|
226
226
|
# Handle functional routes
|
227
|
-
for
|
228
|
-
router.add_route(route=
|
227
|
+
for route in self.functional_handlers:
|
228
|
+
router.add_route(route=route)
|
229
229
|
if not self.endpoint:
|
230
230
|
return router
|
231
231
|
|
@@ -234,9 +234,10 @@ class Route:
|
|
234
234
|
if name.upper() in self.http_methods:
|
235
235
|
sig = inspect.signature(func)
|
236
236
|
doc = self.swagger_generate(sig, func.__doc__)
|
237
|
-
self.endpoint.dispatch.__doc__ = doc
|
238
237
|
endpoint_obj = self.endpoint()
|
239
|
-
|
238
|
+
route = self.make_internal_route(path="/", handler=endpoint_obj.dispatch, method=name.upper())
|
239
|
+
route.doc = doc
|
240
|
+
router.add_route(route=route)
|
240
241
|
del endpoint_obj # free up memory
|
241
242
|
return router
|
242
243
|
|
@@ -250,15 +251,10 @@ class Route:
|
|
250
251
|
return await dispatch(func, request, inject)
|
251
252
|
|
252
253
|
sig = inspect.signature(func)
|
253
|
-
|
254
|
+
route = self.make_internal_route(path=path, handler=functional_wrapper, method=method.upper())
|
255
|
+
route.doc = self.swagger_generate(sig, func.__doc__)
|
254
256
|
|
255
|
-
self.functional_handlers.append(
|
256
|
-
{
|
257
|
-
"path": path,
|
258
|
-
"method": method,
|
259
|
-
"func": functional_wrapper,
|
260
|
-
}
|
261
|
-
)
|
257
|
+
self.functional_handlers.append(route)
|
262
258
|
|
263
259
|
return decorator
|
264
260
|
|