hypern 0.3.11__cp312-cp312-macosx_11_0_arm64.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 +24 -0
- hypern/application.py +495 -0
- hypern/args_parser.py +73 -0
- hypern/auth/__init__.py +0 -0
- hypern/auth/authorization.py +2 -0
- hypern/background.py +4 -0
- hypern/caching/__init__.py +6 -0
- hypern/caching/backend.py +31 -0
- hypern/caching/redis_backend.py +201 -0
- hypern/caching/strategies.py +208 -0
- hypern/cli/__init__.py +0 -0
- hypern/cli/commands.py +0 -0
- hypern/config.py +246 -0
- hypern/database/__init__.py +0 -0
- hypern/database/sqlalchemy/__init__.py +4 -0
- hypern/database/sqlalchemy/config.py +66 -0
- hypern/database/sqlalchemy/repository.py +290 -0
- hypern/database/sqlx/__init__.py +36 -0
- hypern/database/sqlx/field.py +246 -0
- hypern/database/sqlx/migrate.py +263 -0
- hypern/database/sqlx/model.py +117 -0
- hypern/database/sqlx/query.py +904 -0
- hypern/datastructures.py +40 -0
- hypern/enum.py +13 -0
- hypern/exceptions/__init__.py +34 -0
- hypern/exceptions/base.py +62 -0
- hypern/exceptions/common.py +12 -0
- hypern/exceptions/errors.py +15 -0
- hypern/exceptions/formatters.py +56 -0
- hypern/exceptions/http.py +76 -0
- hypern/gateway/__init__.py +6 -0
- hypern/gateway/aggregator.py +32 -0
- hypern/gateway/gateway.py +41 -0
- hypern/gateway/proxy.py +60 -0
- hypern/gateway/service.py +52 -0
- hypern/hypern.cpython-312-darwin.so +0 -0
- hypern/hypern.pyi +333 -0
- hypern/i18n/__init__.py +0 -0
- hypern/logging/__init__.py +3 -0
- hypern/logging/logger.py +82 -0
- hypern/middleware/__init__.py +17 -0
- hypern/middleware/base.py +13 -0
- hypern/middleware/cache.py +177 -0
- hypern/middleware/compress.py +78 -0
- hypern/middleware/cors.py +41 -0
- hypern/middleware/i18n.py +1 -0
- hypern/middleware/limit.py +177 -0
- hypern/middleware/security.py +184 -0
- hypern/openapi/__init__.py +5 -0
- hypern/openapi/schemas.py +51 -0
- hypern/openapi/swagger.py +3 -0
- hypern/processpool.py +139 -0
- hypern/py.typed +0 -0
- hypern/reload.py +46 -0
- hypern/response/__init__.py +3 -0
- hypern/response/response.py +142 -0
- hypern/routing/__init__.py +5 -0
- hypern/routing/dispatcher.py +70 -0
- hypern/routing/endpoint.py +30 -0
- hypern/routing/parser.py +98 -0
- hypern/routing/queue.py +175 -0
- hypern/routing/route.py +280 -0
- hypern/scheduler.py +5 -0
- hypern/worker.py +274 -0
- hypern/ws/__init__.py +4 -0
- hypern/ws/channel.py +80 -0
- hypern/ws/heartbeat.py +74 -0
- hypern/ws/room.py +76 -0
- hypern/ws/route.py +26 -0
- hypern-0.3.11.dist-info/METADATA +134 -0
- hypern-0.3.11.dist-info/RECORD +73 -0
- hypern-0.3.11.dist-info/WHEEL +4 -0
- hypern-0.3.11.dist-info/licenses/LICENSE +24 -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()
|
@@ -0,0 +1,117 @@
|
|
1
|
+
import re
|
2
|
+
from datetime import date, datetime
|
3
|
+
|
4
|
+
from hypern.config import context_store
|
5
|
+
from hypern.exceptions import OutOfScopeApplicationException
|
6
|
+
from hypern.hypern import get_session_database
|
7
|
+
|
8
|
+
from .field import Field, ForeignKeyField
|
9
|
+
from .query import QuerySet
|
10
|
+
|
11
|
+
|
12
|
+
class MetaModel(type):
|
13
|
+
def __new__(mcs, name, bases, attrs):
|
14
|
+
# Skip initialization for base Model class
|
15
|
+
if name == "Model" and not bases:
|
16
|
+
return super().__new__(mcs, name, bases, attrs)
|
17
|
+
|
18
|
+
fields = {}
|
19
|
+
table_name = attrs.get("__tablename__")
|
20
|
+
|
21
|
+
# If table name not specified, convert CamelCase to snake_case
|
22
|
+
if not table_name:
|
23
|
+
table_name = re.sub("(?!^)([A-Z])", r"_\1", name).lower()
|
24
|
+
|
25
|
+
# Collect all fields
|
26
|
+
for key, value in list(attrs.items()):
|
27
|
+
if isinstance(value, Field):
|
28
|
+
fields[key] = value
|
29
|
+
value.name = key
|
30
|
+
|
31
|
+
# Store metadata in class
|
32
|
+
attrs["_fields"] = fields
|
33
|
+
attrs["_table_name"] = table_name
|
34
|
+
|
35
|
+
return super().__new__(mcs, name, bases, attrs)
|
36
|
+
|
37
|
+
|
38
|
+
class Model(metaclass=MetaModel):
|
39
|
+
def __init__(self, **kwargs):
|
40
|
+
self._data = {}
|
41
|
+
# Set default values
|
42
|
+
for name, field in self._fields.items():
|
43
|
+
if field.default is not None:
|
44
|
+
self._data[name] = field.default
|
45
|
+
|
46
|
+
# Set provided values
|
47
|
+
for key, value in kwargs.items():
|
48
|
+
if key in self._fields:
|
49
|
+
self._fields[key].validate(value)
|
50
|
+
self._data[key] = value
|
51
|
+
else:
|
52
|
+
raise ValueError(f"Unknown field {key}")
|
53
|
+
|
54
|
+
@classmethod
|
55
|
+
def get_session(cls):
|
56
|
+
try:
|
57
|
+
context_id = context_store.get_context()
|
58
|
+
except Exception:
|
59
|
+
raise OutOfScopeApplicationException("Context not set")
|
60
|
+
return get_session_database(context_id)
|
61
|
+
|
62
|
+
@classmethod
|
63
|
+
def objects(cls) -> QuerySet:
|
64
|
+
return QuerySet(cls)
|
65
|
+
|
66
|
+
@classmethod
|
67
|
+
def table_name(cls) -> str:
|
68
|
+
return cls._table_name
|
69
|
+
|
70
|
+
@classmethod
|
71
|
+
def create_table_sql(cls) -> str:
|
72
|
+
fields_sql = []
|
73
|
+
indexes_sql = []
|
74
|
+
foreign_keys = []
|
75
|
+
|
76
|
+
for name, field in cls._fields.items():
|
77
|
+
fields_sql.append(cls._get_field_sql(name, field))
|
78
|
+
if field.index:
|
79
|
+
indexes_sql.append(cls._get_index_sql(name))
|
80
|
+
if isinstance(field, ForeignKeyField):
|
81
|
+
foreign_keys.append(cls._get_foreign_key_sql(name, field))
|
82
|
+
|
83
|
+
fields_sql.extend(foreign_keys)
|
84
|
+
joined_fields_sql = ", \n ".join(fields_sql)
|
85
|
+
|
86
|
+
create_table = f"CREATE TABLE {cls.table_name()} (\n {joined_fields_sql} \n"
|
87
|
+
|
88
|
+
return f"{create_table};\n" + ";\n".join(indexes_sql)
|
89
|
+
|
90
|
+
@classmethod
|
91
|
+
def _get_field_sql(cls, name, field) -> str:
|
92
|
+
field_def = [f"{name} {field.sql_type()}"]
|
93
|
+
if field.primary_key:
|
94
|
+
field_def.append("PRIMARY KEY")
|
95
|
+
if not field.null:
|
96
|
+
field_def.append("NOT NULL")
|
97
|
+
if field.unique:
|
98
|
+
field_def.append("UNIQUE")
|
99
|
+
if field.default is not None:
|
100
|
+
if isinstance(field.default, (str, datetime, date)):
|
101
|
+
field_def.append(f"DEFAULT '{field.default}'")
|
102
|
+
else:
|
103
|
+
field_def.append(f"DEFAULT {field.default}")
|
104
|
+
return " ".join(field_def)
|
105
|
+
|
106
|
+
@classmethod
|
107
|
+
def _get_index_sql(cls, name) -> str:
|
108
|
+
return f"CREATE INDEX idx_{cls.table_name()}_{name} ON {cls.table_name()} ({name})"
|
109
|
+
|
110
|
+
@classmethod
|
111
|
+
def _get_foreign_key_sql(cls, name, field) -> str:
|
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}"
|
114
|
+
|
115
|
+
def save(self):
|
116
|
+
query_object = QuerySet(self)
|
117
|
+
query_object.bulk_create([self])
|