future-framework 3.0.0__tar.gz → 3.0.2__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.
- {future_framework-3.0.0 → future_framework-3.0.2}/PKG-INFO +1 -1
- {future_framework-3.0.0 → future_framework-3.0.2}/future/databases/MySQLDatabase.py +21 -4
- {future_framework-3.0.0 → future_framework-3.0.2}/future/databases/PostgresDatabase.py +21 -4
- {future_framework-3.0.0 → future_framework-3.0.2}/future/databases/SQLiteDatabase.py +21 -4
- {future_framework-3.0.0 → future_framework-3.0.2}/future/migrations.py +20 -0
- {future_framework-3.0.0 → future_framework-3.0.2}/future/taskscheduler.py +6 -1
- {future_framework-3.0.0 → future_framework-3.0.2}/pyproject.toml +1 -1
- {future_framework-3.0.0 → future_framework-3.0.2}/LICENSE +0 -0
- {future_framework-3.0.0 → future_framework-3.0.2}/README.md +0 -0
- {future_framework-3.0.0 → future_framework-3.0.2}/future/__init__.py +0 -0
- {future_framework-3.0.0 → future_framework-3.0.2}/future/application.py +0 -0
- {future_framework-3.0.0 → future_framework-3.0.2}/future/authentication/Auth0Authentication.py +0 -0
- {future_framework-3.0.0 → future_framework-3.0.2}/future/authentication/AzureADAuthentication.py +0 -0
- {future_framework-3.0.0 → future_framework-3.0.2}/future/authentication/BasicAuthentication.py +0 -0
- {future_framework-3.0.0 → future_framework-3.0.2}/future/authentication/KerberosAuthentication.py +0 -0
- {future_framework-3.0.0 → future_framework-3.0.2}/future/authentication/KeycloakAuthentication.py +0 -0
- {future_framework-3.0.0 → future_framework-3.0.2}/future/authentication/OAuth2Authentication.py +0 -0
- {future_framework-3.0.0 → future_framework-3.0.2}/future/authentication/OpenIdConnectAuthentication.py +0 -0
- {future_framework-3.0.0 → future_framework-3.0.2}/future/authentication/SAMLAuthentication.py +0 -0
- {future_framework-3.0.0 → future_framework-3.0.2}/future/authentication/__init__.py +0 -0
- {future_framework-3.0.0 → future_framework-3.0.2}/future/cli.py +0 -0
- {future_framework-3.0.0 → future_framework-3.0.2}/future/controllers/DebugController.py +0 -0
- {future_framework-3.0.0 → future_framework-3.0.2}/future/controllers/GraphQLController.py +0 -0
- {future_framework-3.0.0 → future_framework-3.0.2}/future/controllers/OpenAPIController.py +0 -0
- {future_framework-3.0.0 → future_framework-3.0.2}/future/controllers/WebSocketController.py +0 -0
- {future_framework-3.0.0 → future_framework-3.0.2}/future/controllers/WelcomeController.py +0 -0
- {future_framework-3.0.0 → future_framework-3.0.2}/future/controllers/__init__.py +0 -0
- {future_framework-3.0.0 → future_framework-3.0.2}/future/database.py +0 -0
- {future_framework-3.0.0 → future_framework-3.0.2}/future/databases/ClickhouseDatabase.py +0 -0
- {future_framework-3.0.0 → future_framework-3.0.2}/future/databases/ElasticsearchDatabase.py +0 -0
- {future_framework-3.0.0 → future_framework-3.0.2}/future/databases/MongoDBDatabase.py +0 -0
- {future_framework-3.0.0 → future_framework-3.0.2}/future/databases/RedisDatabase.py +0 -0
- {future_framework-3.0.0 → future_framework-3.0.2}/future/databases/__init__.py +0 -0
- {future_framework-3.0.0 → future_framework-3.0.2}/future/exceptions.py +0 -0
- {future_framework-3.0.0 → future_framework-3.0.2}/future/interfaces/IAuthentication.py +0 -0
- {future_framework-3.0.0 → future_framework-3.0.2}/future/interfaces/IController.py +0 -0
- {future_framework-3.0.0 → future_framework-3.0.2}/future/interfaces/IDatabase.py +0 -0
- {future_framework-3.0.0 → future_framework-3.0.2}/future/interfaces/IMiddleware.py +0 -0
- {future_framework-3.0.0 → future_framework-3.0.2}/future/interfaces/IModel.py +0 -0
- {future_framework-3.0.0 → future_framework-3.0.2}/future/interfaces/IPlugin.py +0 -0
- {future_framework-3.0.0 → future_framework-3.0.2}/future/interfaces/ITask.py +0 -0
- {future_framework-3.0.0 → future_framework-3.0.2}/future/interfaces/__init__.py +0 -0
- {future_framework-3.0.0 → future_framework-3.0.2}/future/interfacing.py +0 -0
- {future_framework-3.0.0 → future_framework-3.0.2}/future/lifespan.py +0 -0
- {future_framework-3.0.0 → future_framework-3.0.2}/future/logger.py +0 -0
- {future_framework-3.0.0 → future_framework-3.0.2}/future/middleware/BruteforcePreventionMiddleware.py +0 -0
- {future_framework-3.0.0 → future_framework-3.0.2}/future/middleware/CORSMiddleware.py +0 -0
- {future_framework-3.0.0 → future_framework-3.0.2}/future/middleware/CSRFMiddleware.py +0 -0
- {future_framework-3.0.0 → future_framework-3.0.2}/future/middleware/GZipMiddleware.py +0 -0
- {future_framework-3.0.0 → future_framework-3.0.2}/future/middleware/HTAccessConfuserMiddleware.py +0 -0
- {future_framework-3.0.0 → future_framework-3.0.2}/future/middleware/HeaderConfuserMiddleware.py +0 -0
- {future_framework-3.0.0 → future_framework-3.0.2}/future/middleware/RateLimitMiddleware.py +0 -0
- {future_framework-3.0.0 → future_framework-3.0.2}/future/middleware/ResponseCodeConfuserMiddleware.py +0 -0
- {future_framework-3.0.0 → future_framework-3.0.2}/future/middleware/SQLiConfuserMiddleware.py +0 -0
- {future_framework-3.0.0 → future_framework-3.0.2}/future/middleware/ScopeValidationMiddleware.py +0 -0
- {future_framework-3.0.0 → future_framework-3.0.2}/future/middleware/SessionMiddleware.py +0 -0
- {future_framework-3.0.0 → future_framework-3.0.2}/future/middleware/TestMiddlewareRequest.py +0 -0
- {future_framework-3.0.0 → future_framework-3.0.2}/future/middleware/TestMiddlewareResponse.py +0 -0
- {future_framework-3.0.0 → future_framework-3.0.2}/future/middleware/WebServerConfuserMiddleware.py +0 -0
- {future_framework-3.0.0 → future_framework-3.0.2}/future/middleware/__init__.py +0 -0
- {future_framework-3.0.0 → future_framework-3.0.2}/future/models/PostModel.py +0 -0
- {future_framework-3.0.0 → future_framework-3.0.2}/future/models/Query.py +0 -0
- {future_framework-3.0.0 → future_framework-3.0.2}/future/models/UserModel.py +0 -0
- {future_framework-3.0.0 → future_framework-3.0.2}/future/models/__init__.py +0 -0
- {future_framework-3.0.0 → future_framework-3.0.2}/future/openapi.py +0 -0
- {future_framework-3.0.0 → future_framework-3.0.2}/future/plugins/ElasticsearchPlugin.py +0 -0
- {future_framework-3.0.0 → future_framework-3.0.2}/future/plugins/__init__.py +0 -0
- {future_framework-3.0.0 → future_framework-3.0.2}/future/request.py +0 -0
- {future_framework-3.0.0 → future_framework-3.0.2}/future/response.py +0 -0
- {future_framework-3.0.0 → future_framework-3.0.2}/future/routing.py +0 -0
- {future_framework-3.0.0 → future_framework-3.0.2}/future/seeder.py +0 -0
- {future_framework-3.0.0 → future_framework-3.0.2}/future/settings.py +0 -0
- {future_framework-3.0.0 → future_framework-3.0.2}/future/tasks/CheckDNSTask.py +0 -0
- {future_framework-3.0.0 → future_framework-3.0.2}/future/tasks/CheckDiskUsageTask.py +0 -0
- {future_framework-3.0.0 → future_framework-3.0.2}/future/tasks/CheckHttpStatusTask.py +0 -0
- {future_framework-3.0.0 → future_framework-3.0.2}/future/tasks/CheckMemoryUsageTask.py +0 -0
- {future_framework-3.0.0 → future_framework-3.0.2}/future/tasks/CheckSSHBannerTask.py +0 -0
- {future_framework-3.0.0 → future_framework-3.0.2}/future/tasks/CheckSystemUptimeTask.py +0 -0
- {future_framework-3.0.0 → future_framework-3.0.2}/future/tasks/DailyBackupTask.py +0 -0
- {future_framework-3.0.0 → future_framework-3.0.2}/future/tasks/SyncTaskExample.py +0 -0
- {future_framework-3.0.0 → future_framework-3.0.2}/future/tasks/__init__.py +0 -0
- {future_framework-3.0.0 → future_framework-3.0.2}/future/testclient.py +0 -0
- {future_framework-3.0.0 → future_framework-3.0.2}/future/types.py +0 -0
|
@@ -172,7 +172,6 @@ class MySQLDatabase(IDatabase):
|
|
|
172
172
|
async with self.client.connect() as connection:
|
|
173
173
|
rows = (await connection.execute(sql, {"table": blueprint.name})).mappings().all()
|
|
174
174
|
existing = {row["COLUMN_NAME"] for row in rows}
|
|
175
|
-
common = [column.name for column in blueprint.columns if column.name in existing]
|
|
176
175
|
temporary = f"_future_migrate_{blueprint.name}"
|
|
177
176
|
backup = f"_future_backup_{blueprint.name}"
|
|
178
177
|
parts = self._schema_parts(blueprint)
|
|
@@ -180,9 +179,27 @@ class MySQLDatabase(IDatabase):
|
|
|
180
179
|
await connection.execute(text(f"DROP TABLE IF EXISTS `{temporary}`"))
|
|
181
180
|
await connection.execute(text(f"DROP TABLE IF EXISTS `{backup}`"))
|
|
182
181
|
await connection.execute(text(f"CREATE TABLE `{temporary}` ({', '.join(parts)})"))
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
182
|
+
targets = []
|
|
183
|
+
sources = []
|
|
184
|
+
backfills = {}
|
|
185
|
+
for index, column in enumerate(blueprint.columns):
|
|
186
|
+
quoted = f"`{column.name}`"
|
|
187
|
+
if column.name in existing:
|
|
188
|
+
source = quoted
|
|
189
|
+
if not column.is_nullable:
|
|
190
|
+
key = f"future_backfill_{index}"
|
|
191
|
+
source = f"COALESCE({quoted}, :{key})"
|
|
192
|
+
backfills[key] = column.backfill_value()
|
|
193
|
+
elif not column.is_nullable:
|
|
194
|
+
key = f"future_backfill_{index}"
|
|
195
|
+
source = f":{key}"
|
|
196
|
+
backfills[key] = column.backfill_value()
|
|
197
|
+
else:
|
|
198
|
+
continue
|
|
199
|
+
targets.append(quoted)
|
|
200
|
+
sources.append(source)
|
|
201
|
+
if targets:
|
|
202
|
+
await connection.execute(text(f"INSERT INTO `{temporary}` ({', '.join(targets)}) SELECT {', '.join(sources)} FROM `{blueprint.name}`"), backfills)
|
|
186
203
|
await connection.execute(text(f"RENAME TABLE `{blueprint.name}` TO `{backup}`, `{temporary}` TO `{blueprint.name}`"))
|
|
187
204
|
await connection.execute(text(f"DROP TABLE `{backup}`"))
|
|
188
205
|
return {"status": "updated", "table": blueprint.name, "columns": sorted(column.name for column in blueprint.columns)}
|
|
@@ -180,15 +180,32 @@ class PostgresDatabase(IDatabase):
|
|
|
180
180
|
async with self.client.connect() as connection:
|
|
181
181
|
rows = (await connection.execute(sql, {"table": blueprint.name})).mappings().all()
|
|
182
182
|
existing = {row["column_name"] for row in rows}
|
|
183
|
-
common = [column.name for column in blueprint.columns if column.name in existing]
|
|
184
183
|
temporary = f"_future_{uuid4().hex[:8]}_{blueprint.name}"[:63]
|
|
185
184
|
parts, indexes = self._schema_parts(blueprint)
|
|
186
185
|
async with self.client.begin() as connection:
|
|
187
186
|
await connection.execute(text(f'DROP TABLE IF EXISTS "{temporary}"'))
|
|
188
187
|
await connection.execute(text(f'CREATE TABLE "{temporary}" ({", ".join(parts)})'))
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
188
|
+
targets = []
|
|
189
|
+
sources = []
|
|
190
|
+
backfills = {}
|
|
191
|
+
for index, column in enumerate(blueprint.columns):
|
|
192
|
+
quoted = f'"{column.name}"'
|
|
193
|
+
if column.name in existing:
|
|
194
|
+
source = quoted
|
|
195
|
+
if not column.is_nullable:
|
|
196
|
+
key = f"future_backfill_{index}"
|
|
197
|
+
source = f"COALESCE({quoted}, :{key})"
|
|
198
|
+
backfills[key] = column.backfill_value()
|
|
199
|
+
elif not column.is_nullable:
|
|
200
|
+
key = f"future_backfill_{index}"
|
|
201
|
+
source = f":{key}"
|
|
202
|
+
backfills[key] = column.backfill_value()
|
|
203
|
+
else:
|
|
204
|
+
continue
|
|
205
|
+
targets.append(quoted)
|
|
206
|
+
sources.append(source)
|
|
207
|
+
if targets:
|
|
208
|
+
await connection.execute(text(f'INSERT INTO "{temporary}" ({", ".join(targets)}) SELECT {", ".join(sources)} FROM "{blueprint.name}"'), backfills)
|
|
192
209
|
await connection.execute(text(f'DROP TABLE "{blueprint.name}"'))
|
|
193
210
|
await connection.execute(text(f'ALTER TABLE "{temporary}" RENAME TO "{blueprint.name}"'))
|
|
194
211
|
for index_name in indexes:
|
|
@@ -178,16 +178,33 @@ class SQLiteDatabase(IDatabase):
|
|
|
178
178
|
rows = (await connection.execute(text(f'PRAGMA table_info("{blueprint.name}")'))).mappings().all()
|
|
179
179
|
existing = {row["name"] for row in rows}
|
|
180
180
|
desired = {column.name for column in blueprint.columns}
|
|
181
|
-
common = [column.name for column in blueprint.columns if column.name in existing]
|
|
182
181
|
temporary = f"_future_migrate_{blueprint.name}"
|
|
183
182
|
definitions, _ = self._schema_parts(blueprint)
|
|
184
183
|
_, indexes = self._schema_parts(blueprint, index_table=blueprint.name)
|
|
185
184
|
async with self.client.begin() as connection:
|
|
186
185
|
await connection.execute(text(f'DROP TABLE IF EXISTS "{temporary}"'))
|
|
187
186
|
await connection.execute(text(f'CREATE TABLE "{temporary}" ({", ".join(definitions)})'))
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
187
|
+
targets = []
|
|
188
|
+
sources = []
|
|
189
|
+
backfills = {}
|
|
190
|
+
for index, column in enumerate(blueprint.columns):
|
|
191
|
+
quoted = f'"{column.name}"'
|
|
192
|
+
if column.name in existing:
|
|
193
|
+
source = quoted
|
|
194
|
+
if not column.is_nullable:
|
|
195
|
+
key = f"future_backfill_{index}"
|
|
196
|
+
source = f"COALESCE({quoted}, :{key})"
|
|
197
|
+
backfills[key] = column.backfill_value()
|
|
198
|
+
elif not column.is_nullable:
|
|
199
|
+
key = f"future_backfill_{index}"
|
|
200
|
+
source = f":{key}"
|
|
201
|
+
backfills[key] = column.backfill_value()
|
|
202
|
+
else:
|
|
203
|
+
continue
|
|
204
|
+
targets.append(quoted)
|
|
205
|
+
sources.append(source)
|
|
206
|
+
if targets:
|
|
207
|
+
await connection.execute(text(f'INSERT INTO "{temporary}" ({", ".join(targets)}) SELECT {", ".join(sources)} FROM "{blueprint.name}"'), backfills)
|
|
191
208
|
await connection.execute(text(f'DROP TABLE "{blueprint.name}"'))
|
|
192
209
|
await connection.execute(text(f'ALTER TABLE "{temporary}" RENAME TO "{blueprint.name}"'))
|
|
193
210
|
for index_sql in indexes:
|
|
@@ -55,6 +55,21 @@ class Column:
|
|
|
55
55
|
self.default_value = value
|
|
56
56
|
return self
|
|
57
57
|
|
|
58
|
+
def backfill_value(self):
|
|
59
|
+
if self.default_value is not None:
|
|
60
|
+
return self.default_value
|
|
61
|
+
if self.type in ("string", "text"):
|
|
62
|
+
return ""
|
|
63
|
+
if self.type == "integer":
|
|
64
|
+
return 0
|
|
65
|
+
if self.type == "float":
|
|
66
|
+
return 0.0
|
|
67
|
+
if self.type == "boolean":
|
|
68
|
+
return False
|
|
69
|
+
if self.type == "datetime":
|
|
70
|
+
return datetime(1970, 1, 1)
|
|
71
|
+
raise ValueError(f"Unsupported column type: {self.type}")
|
|
72
|
+
|
|
58
73
|
|
|
59
74
|
class Blueprint:
|
|
60
75
|
def __init__(self, name, connection_name, action="create"):
|
|
@@ -268,6 +283,11 @@ class MigrationGenerator:
|
|
|
268
283
|
for field, annotation in annotations.items():
|
|
269
284
|
line = self.column_line(field, annotation)
|
|
270
285
|
if line is not None:
|
|
286
|
+
default_value = vars(model).get(field)
|
|
287
|
+
if default_value is not None:
|
|
288
|
+
if isinstance(default_value, datetime):
|
|
289
|
+
default_value = default_value.isoformat(sep=" ")
|
|
290
|
+
line += f".default({default_value!r})"
|
|
271
291
|
lines.append(f" {line}")
|
|
272
292
|
if use_timestamps:
|
|
273
293
|
lines.append(" table.timestamps()")
|
|
@@ -27,6 +27,7 @@ class ScheduledTask:
|
|
|
27
27
|
self.jitter = task.jitter
|
|
28
28
|
self.last_run: Optional[datetime] = None
|
|
29
29
|
self.next_run: Optional[datetime] = None
|
|
30
|
+
self.running = False
|
|
30
31
|
|
|
31
32
|
if self.start_time is None:
|
|
32
33
|
self.start_time = datetime.now()
|
|
@@ -85,6 +86,7 @@ class CronScheduler:
|
|
|
85
86
|
|
|
86
87
|
async def _run_task(self, scheduled: ScheduledTask) -> None:
|
|
87
88
|
"""Run a single task."""
|
|
89
|
+
scheduled.running = True
|
|
88
90
|
try:
|
|
89
91
|
log.debug(f"Running scheduled task '{scheduled.name}'")
|
|
90
92
|
await scheduled.task.run()
|
|
@@ -99,6 +101,8 @@ class CronScheduler:
|
|
|
99
101
|
except Exception as e:
|
|
100
102
|
log.error(f"Error running scheduled task '{scheduled.name}': {e}")
|
|
101
103
|
# Don't update last_run on error, so it will retry next cycle
|
|
104
|
+
finally:
|
|
105
|
+
scheduled.running = False
|
|
102
106
|
|
|
103
107
|
async def _scheduler_loop(self) -> None:
|
|
104
108
|
"""Main scheduler loop that checks for tasks to run."""
|
|
@@ -110,7 +114,8 @@ class CronScheduler:
|
|
|
110
114
|
|
|
111
115
|
# Check which tasks need to run
|
|
112
116
|
for scheduled in self.tasks.values():
|
|
113
|
-
if scheduled.next_run and now >= scheduled.next_run:
|
|
117
|
+
if scheduled.next_run and now >= scheduled.next_run and not scheduled.running:
|
|
118
|
+
scheduled.running = True
|
|
114
119
|
tasks_to_run.append(scheduled)
|
|
115
120
|
|
|
116
121
|
# Run tasks that are due
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{future_framework-3.0.0 → future_framework-3.0.2}/future/authentication/Auth0Authentication.py
RENAMED
|
File without changes
|
{future_framework-3.0.0 → future_framework-3.0.2}/future/authentication/AzureADAuthentication.py
RENAMED
|
File without changes
|
{future_framework-3.0.0 → future_framework-3.0.2}/future/authentication/BasicAuthentication.py
RENAMED
|
File without changes
|
{future_framework-3.0.0 → future_framework-3.0.2}/future/authentication/KerberosAuthentication.py
RENAMED
|
File without changes
|
{future_framework-3.0.0 → future_framework-3.0.2}/future/authentication/KeycloakAuthentication.py
RENAMED
|
File without changes
|
{future_framework-3.0.0 → future_framework-3.0.2}/future/authentication/OAuth2Authentication.py
RENAMED
|
File without changes
|
|
File without changes
|
{future_framework-3.0.0 → future_framework-3.0.2}/future/authentication/SAMLAuthentication.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{future_framework-3.0.0 → future_framework-3.0.2}/future/middleware/HTAccessConfuserMiddleware.py
RENAMED
|
File without changes
|
{future_framework-3.0.0 → future_framework-3.0.2}/future/middleware/HeaderConfuserMiddleware.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{future_framework-3.0.0 → future_framework-3.0.2}/future/middleware/SQLiConfuserMiddleware.py
RENAMED
|
File without changes
|
{future_framework-3.0.0 → future_framework-3.0.2}/future/middleware/ScopeValidationMiddleware.py
RENAMED
|
File without changes
|
|
File without changes
|
{future_framework-3.0.0 → future_framework-3.0.2}/future/middleware/TestMiddlewareRequest.py
RENAMED
|
File without changes
|
{future_framework-3.0.0 → future_framework-3.0.2}/future/middleware/TestMiddlewareResponse.py
RENAMED
|
File without changes
|
{future_framework-3.0.0 → future_framework-3.0.2}/future/middleware/WebServerConfuserMiddleware.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|