future-framework 3.1.0__tar.gz → 3.1.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.
Files changed (86) hide show
  1. {future_framework-3.1.0 → future_framework-3.1.2}/PKG-INFO +1 -1
  2. {future_framework-3.1.0 → future_framework-3.1.2}/future/application.py +2 -1
  3. future_framework-3.1.2/future/logger.py +127 -0
  4. {future_framework-3.1.0 → future_framework-3.1.2}/pyproject.toml +1 -1
  5. {future_framework-3.1.0 → future_framework-3.1.2}/pyproject.toml.orig +1 -1
  6. future_framework-3.1.0/future/logger.py +0 -53
  7. {future_framework-3.1.0 → future_framework-3.1.2}/LICENSE +0 -0
  8. {future_framework-3.1.0 → future_framework-3.1.2}/README.md +0 -0
  9. {future_framework-3.1.0 → future_framework-3.1.2}/future/__init__.py +0 -0
  10. {future_framework-3.1.0 → future_framework-3.1.2}/future/authentication/Auth0Authentication.py +0 -0
  11. {future_framework-3.1.0 → future_framework-3.1.2}/future/authentication/AzureADAuthentication.py +0 -0
  12. {future_framework-3.1.0 → future_framework-3.1.2}/future/authentication/BasicAuthentication.py +0 -0
  13. {future_framework-3.1.0 → future_framework-3.1.2}/future/authentication/KerberosAuthentication.py +0 -0
  14. {future_framework-3.1.0 → future_framework-3.1.2}/future/authentication/KeycloakAuthentication.py +0 -0
  15. {future_framework-3.1.0 → future_framework-3.1.2}/future/authentication/OAuth2Authentication.py +0 -0
  16. {future_framework-3.1.0 → future_framework-3.1.2}/future/authentication/OpenIdConnectAuthentication.py +0 -0
  17. {future_framework-3.1.0 → future_framework-3.1.2}/future/authentication/SAMLAuthentication.py +0 -0
  18. {future_framework-3.1.0 → future_framework-3.1.2}/future/authentication/__init__.py +0 -0
  19. {future_framework-3.1.0 → future_framework-3.1.2}/future/cli.py +0 -0
  20. {future_framework-3.1.0 → future_framework-3.1.2}/future/controllers/DebugController.py +0 -0
  21. {future_framework-3.1.0 → future_framework-3.1.2}/future/controllers/GraphQLController.py +0 -0
  22. {future_framework-3.1.0 → future_framework-3.1.2}/future/controllers/OpenAPIController.py +0 -0
  23. {future_framework-3.1.0 → future_framework-3.1.2}/future/controllers/WebSocketController.py +0 -0
  24. {future_framework-3.1.0 → future_framework-3.1.2}/future/controllers/WelcomeController.py +0 -0
  25. {future_framework-3.1.0 → future_framework-3.1.2}/future/controllers/__init__.py +0 -0
  26. {future_framework-3.1.0 → future_framework-3.1.2}/future/database.py +0 -0
  27. {future_framework-3.1.0 → future_framework-3.1.2}/future/databases/ClickhouseDatabase.py +0 -0
  28. {future_framework-3.1.0 → future_framework-3.1.2}/future/databases/ElasticsearchDatabase.py +0 -0
  29. {future_framework-3.1.0 → future_framework-3.1.2}/future/databases/MongoDBDatabase.py +0 -0
  30. {future_framework-3.1.0 → future_framework-3.1.2}/future/databases/MySQLDatabase.py +0 -0
  31. {future_framework-3.1.0 → future_framework-3.1.2}/future/databases/PostgresDatabase.py +0 -0
  32. {future_framework-3.1.0 → future_framework-3.1.2}/future/databases/RedisDatabase.py +0 -0
  33. {future_framework-3.1.0 → future_framework-3.1.2}/future/databases/SQLiteDatabase.py +0 -0
  34. {future_framework-3.1.0 → future_framework-3.1.2}/future/databases/__init__.py +0 -0
  35. {future_framework-3.1.0 → future_framework-3.1.2}/future/exceptions.py +0 -0
  36. {future_framework-3.1.0 → future_framework-3.1.2}/future/interfaces/IAuthentication.py +0 -0
  37. {future_framework-3.1.0 → future_framework-3.1.2}/future/interfaces/IController.py +0 -0
  38. {future_framework-3.1.0 → future_framework-3.1.2}/future/interfaces/IDatabase.py +0 -0
  39. {future_framework-3.1.0 → future_framework-3.1.2}/future/interfaces/IMiddleware.py +0 -0
  40. {future_framework-3.1.0 → future_framework-3.1.2}/future/interfaces/IModel.py +0 -0
  41. {future_framework-3.1.0 → future_framework-3.1.2}/future/interfaces/IPlugin.py +0 -0
  42. {future_framework-3.1.0 → future_framework-3.1.2}/future/interfaces/ITask.py +0 -0
  43. {future_framework-3.1.0 → future_framework-3.1.2}/future/interfaces/__init__.py +0 -0
  44. {future_framework-3.1.0 → future_framework-3.1.2}/future/interfacing.py +0 -0
  45. {future_framework-3.1.0 → future_framework-3.1.2}/future/lifespan.py +0 -0
  46. {future_framework-3.1.0 → future_framework-3.1.2}/future/middleware/BruteforcePreventionMiddleware.py +0 -0
  47. {future_framework-3.1.0 → future_framework-3.1.2}/future/middleware/CORSMiddleware.py +0 -0
  48. {future_framework-3.1.0 → future_framework-3.1.2}/future/middleware/CSRFMiddleware.py +0 -0
  49. {future_framework-3.1.0 → future_framework-3.1.2}/future/middleware/GZipMiddleware.py +0 -0
  50. {future_framework-3.1.0 → future_framework-3.1.2}/future/middleware/HTAccessConfuserMiddleware.py +0 -0
  51. {future_framework-3.1.0 → future_framework-3.1.2}/future/middleware/HeaderConfuserMiddleware.py +0 -0
  52. {future_framework-3.1.0 → future_framework-3.1.2}/future/middleware/RateLimitMiddleware.py +0 -0
  53. {future_framework-3.1.0 → future_framework-3.1.2}/future/middleware/ResponseCodeConfuserMiddleware.py +0 -0
  54. {future_framework-3.1.0 → future_framework-3.1.2}/future/middleware/SQLiConfuserMiddleware.py +0 -0
  55. {future_framework-3.1.0 → future_framework-3.1.2}/future/middleware/ScopeValidationMiddleware.py +0 -0
  56. {future_framework-3.1.0 → future_framework-3.1.2}/future/middleware/SessionMiddleware.py +0 -0
  57. {future_framework-3.1.0 → future_framework-3.1.2}/future/middleware/TestMiddlewareRequest.py +0 -0
  58. {future_framework-3.1.0 → future_framework-3.1.2}/future/middleware/TestMiddlewareResponse.py +0 -0
  59. {future_framework-3.1.0 → future_framework-3.1.2}/future/middleware/WebServerConfuserMiddleware.py +0 -0
  60. {future_framework-3.1.0 → future_framework-3.1.2}/future/middleware/__init__.py +0 -0
  61. {future_framework-3.1.0 → future_framework-3.1.2}/future/migrations.py +0 -0
  62. {future_framework-3.1.0 → future_framework-3.1.2}/future/models/PostModel.py +0 -0
  63. {future_framework-3.1.0 → future_framework-3.1.2}/future/models/Query.py +0 -0
  64. {future_framework-3.1.0 → future_framework-3.1.2}/future/models/UserModel.py +0 -0
  65. {future_framework-3.1.0 → future_framework-3.1.2}/future/models/__init__.py +0 -0
  66. {future_framework-3.1.0 → future_framework-3.1.2}/future/openapi.py +0 -0
  67. {future_framework-3.1.0 → future_framework-3.1.2}/future/plugins/ElasticsearchPlugin.py +0 -0
  68. {future_framework-3.1.0 → future_framework-3.1.2}/future/plugins/__init__.py +0 -0
  69. {future_framework-3.1.0 → future_framework-3.1.2}/future/request.py +0 -0
  70. {future_framework-3.1.0 → future_framework-3.1.2}/future/response.py +0 -0
  71. {future_framework-3.1.0 → future_framework-3.1.2}/future/routing.py +0 -0
  72. {future_framework-3.1.0 → future_framework-3.1.2}/future/scheduling.py +0 -0
  73. {future_framework-3.1.0 → future_framework-3.1.2}/future/seeder.py +0 -0
  74. {future_framework-3.1.0 → future_framework-3.1.2}/future/settings.py +0 -0
  75. {future_framework-3.1.0 → future_framework-3.1.2}/future/tasks/CheckDNSTask.py +0 -0
  76. {future_framework-3.1.0 → future_framework-3.1.2}/future/tasks/CheckDiskUsageTask.py +0 -0
  77. {future_framework-3.1.0 → future_framework-3.1.2}/future/tasks/CheckHttpStatusTask.py +0 -0
  78. {future_framework-3.1.0 → future_framework-3.1.2}/future/tasks/CheckMemoryUsageTask.py +0 -0
  79. {future_framework-3.1.0 → future_framework-3.1.2}/future/tasks/CheckSSHBannerTask.py +0 -0
  80. {future_framework-3.1.0 → future_framework-3.1.2}/future/tasks/CheckSystemUptimeTask.py +0 -0
  81. {future_framework-3.1.0 → future_framework-3.1.2}/future/tasks/DailyBackupTask.py +0 -0
  82. {future_framework-3.1.0 → future_framework-3.1.2}/future/tasks/SyncTaskExample.py +0 -0
  83. {future_framework-3.1.0 → future_framework-3.1.2}/future/tasks/__init__.py +0 -0
  84. {future_framework-3.1.0 → future_framework-3.1.2}/future/taskscheduler.py +0 -0
  85. {future_framework-3.1.0 → future_framework-3.1.2}/future/testclient.py +0 -0
  86. {future_framework-3.1.0 → future_framework-3.1.2}/future/types.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: future-framework
3
- Version: 3.1.0
3
+ Version: 3.1.2
4
4
  Summary: ASGI-based framework for web APIs
5
5
  Author: nicolaipre
6
6
  License-Expression: MIT
@@ -16,7 +16,7 @@ from rich.table import Table
16
16
  from rich.text import Text
17
17
 
18
18
  from future.exceptions import ErrorHandler, HTTPException
19
- from future.logger import log
19
+ from future.logger import create_uvicorn_logging_config, log
20
20
  from future.interfaces.IMiddleware import IMiddleware
21
21
  from future.openapi import get_openapi_config, is_docs_path, openapi_routes, rebuild_spec_from_routes, set_openapi_config, spec_path
22
22
  from future.request import Request
@@ -656,6 +656,7 @@ class Future:
656
656
  ssl_certfile=tls_cert,
657
657
  ssl_keyfile_password=tls_password,
658
658
  timeout_graceful_shutdown=3,
659
+ log_config=create_uvicorn_logging_config(),
659
660
  log_level="info",
660
661
  lifespan="on",
661
662
  access_log=access_log,
@@ -0,0 +1,127 @@
1
+ import atexit
2
+ import http
3
+ import logging
4
+ import logging.config
5
+ import logging.handlers
6
+
7
+ from datetime import datetime, timezone
8
+ from typing import Any
9
+
10
+ from uvicorn.logging import AccessFormatter, DefaultFormatter
11
+
12
+ from future.settings import LOGGING_CONFIG
13
+
14
+
15
+ RESET = "\033[0m"
16
+ LEVEL_COLORS = {
17
+ logging.DEBUG: "\033[36m", # Cyan
18
+ logging.INFO: "\033[32m", # Green
19
+ logging.WARNING: "\033[38;5;208m", # Orange
20
+ logging.ERROR: "\033[31m", # Red
21
+ logging.CRITICAL: "\033[91m", # Bright red
22
+ }
23
+ HTTP_STATUS_COLORS = {
24
+ 1: "\033[96m", # Bright cyan
25
+ 2: "\033[97m", # Bright white
26
+ 3: "\033[92m", # Bright green
27
+ 4: "\033[91m", # Bright red
28
+ 5: "\033[94m", # Bright blue
29
+ }
30
+ DEFAULT_LOG_FORMAT = "%(asctime)s %(levelprefix)s %(message)s"
31
+ ACCESS_LOG_FORMAT = '%(asctime)s %(levelprefix)s %(client_addr)s - "%(request_line)s" %(status_code)s'
32
+
33
+
34
+ def _colorize(value: str, color: str | None) -> str:
35
+ return f"{color}{value}{RESET}" if color else value
36
+
37
+
38
+ class UtcTimestampFormatter:
39
+ """Render log timestamps as unambiguous ISO 8601 UTC values."""
40
+
41
+ def formatTime(self, record: logging.LogRecord, datefmt: str | None = None) -> str:
42
+ return datetime.fromtimestamp(record.created, timezone.utc).isoformat(timespec="milliseconds").replace("+00:00", "Z")
43
+
44
+ def color_level_name(self, level_name: str, level_no: int) -> str:
45
+ return _colorize(level_name, LEVEL_COLORS.get(level_no))
46
+
47
+
48
+ class ColoredFormatter(UtcTimestampFormatter, DefaultFormatter):
49
+ """Shared timestamp and level formatter for Future and Uvicorn."""
50
+
51
+
52
+ class UvicornDefaultFormatter(UtcTimestampFormatter, DefaultFormatter):
53
+ """Uvicorn server formatter using Future's UTC timestamp format."""
54
+
55
+
56
+ class UvicornAccessFormatter(UtcTimestampFormatter, AccessFormatter):
57
+ """Uvicorn access formatter using Future's UTC timestamp format."""
58
+
59
+ def get_status_code(self, status_code: int) -> str:
60
+ try:
61
+ status_phrase = http.HTTPStatus(status_code).phrase
62
+ except ValueError:
63
+ status_phrase = ""
64
+ status_and_phrase = f"{status_code} {status_phrase}"
65
+ if not self.use_colors:
66
+ return status_and_phrase
67
+ return _colorize(status_and_phrase, HTTP_STATUS_COLORS.get(status_code // 100))
68
+
69
+
70
+ def create_uvicorn_logging_config() -> dict[str, Any]:
71
+ """Return a fresh Uvicorn logging config for each server invocation."""
72
+ return {
73
+ "version": 1,
74
+ "disable_existing_loggers": False,
75
+ "formatters": {
76
+ "default": {
77
+ "()": UvicornDefaultFormatter,
78
+ "fmt": DEFAULT_LOG_FORMAT,
79
+ "use_colors": True,
80
+ },
81
+ "access": {
82
+ "()": UvicornAccessFormatter,
83
+ "fmt": ACCESS_LOG_FORMAT,
84
+ "use_colors": True,
85
+ },
86
+ },
87
+ "handlers": {
88
+ "default": {
89
+ "formatter": "default",
90
+ "class": "logging.StreamHandler",
91
+ "stream": "ext://sys.stderr",
92
+ },
93
+ "access": {
94
+ "formatter": "access",
95
+ "class": "logging.StreamHandler",
96
+ "stream": "ext://sys.stdout",
97
+ },
98
+ },
99
+ "loggers": {
100
+ "uvicorn": {"handlers": ["default"], "level": "INFO", "propagate": False},
101
+ "uvicorn.error": {"level": "INFO"},
102
+ "uvicorn.access": {"handlers": ["access"], "level": "INFO", "propagate": False},
103
+ },
104
+ }
105
+
106
+
107
+ # Configure logging using settings
108
+ logging.config.dictConfig(LOGGING_CONFIG)
109
+
110
+ log = logging.getLogger("future")
111
+ formatter = ColoredFormatter(DEFAULT_LOG_FORMAT, use_colors=True)
112
+
113
+ # QueueHandler itself does not format; the listener's stdout handler does.
114
+ # Handler level NOTSET so Future's log.setLevel(APP_DEBUG) alone controls filtering.
115
+ queue_handler = logging.getHandlerByName("queue_handler")
116
+ if queue_handler is not None and isinstance(queue_handler, logging.handlers.QueueHandler):
117
+ listener = getattr(queue_handler, "listener", None)
118
+ if listener is not None:
119
+ for handler in listener.handlers:
120
+ handler.setFormatter(formatter)
121
+ handler.setLevel(logging.NOTSET)
122
+ listener.start()
123
+ atexit.register(listener.stop)
124
+ else:
125
+ for handler in log.handlers:
126
+ handler.setFormatter(formatter)
127
+ handler.setLevel(logging.NOTSET)
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "future-framework"
3
- version = "3.1.0"
3
+ version = "3.1.2"
4
4
  description = "ASGI-based framework for web APIs"
5
5
  license = "MIT"
6
6
  license-files = ["LICENSE"]
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "future-framework"
3
- version = "3.1.0"
3
+ version = "3.1.2"
4
4
  description = "ASGI-based framework for web APIs"
5
5
  authors = [{ name = "nicolaipre" }]
6
6
  license = "MIT"
@@ -1,53 +0,0 @@
1
- import atexit
2
- import logging
3
- import logging.config
4
- import logging.handlers
5
-
6
- from future.settings import LOGGING_CONFIG
7
-
8
-
9
- # ANSI color codes
10
- class ColoredFormatter(logging.Formatter):
11
- """Custom formatter with colors like uvicorn"""
12
-
13
- COLORS = {
14
- "DEBUG": "\033[36m", # Cyan
15
- "INFO": "\033[32m", # Green
16
- "WARNING": "\033[33m", # Yellow
17
- "ERROR": "\033[31m", # Red
18
- "CRITICAL": "\033[35m", # Magenta
19
- }
20
- RESET = "\033[0m"
21
-
22
- def format(self, record: logging.LogRecord) -> str:
23
- # Add color to levelname
24
- levelname = record.levelname
25
- if levelname in self.COLORS:
26
- # Calculate padding based on original levelname length BEFORE adding colors
27
- padding = " " * (9 - len(levelname)) # 9 is max level length (CRITICAL) + 1
28
- colored_levelname = f"{self.COLORS[levelname]}{levelname}{self.RESET}"
29
- return f"{colored_levelname}:{padding}{record.getMessage()}"
30
- return super().format(record)
31
-
32
-
33
- # Configure logging using settings
34
- logging.config.dictConfig(LOGGING_CONFIG)
35
-
36
- log = logging.getLogger("future")
37
- formatter = ColoredFormatter("%(message)s")
38
-
39
- # QueueHandler itself does not format; the listener's stdout handler does.
40
- # Handler level NOTSET so Future's log.setLevel(APP_DEBUG) alone controls filtering.
41
- queue_handler = logging.getHandlerByName("queue_handler")
42
- if queue_handler is not None and isinstance(queue_handler, logging.handlers.QueueHandler):
43
- listener = getattr(queue_handler, "listener", None)
44
- if listener is not None:
45
- for handler in listener.handlers:
46
- handler.setFormatter(formatter)
47
- handler.setLevel(logging.NOTSET)
48
- listener.start()
49
- atexit.register(listener.stop)
50
- else:
51
- for handler in log.handlers:
52
- handler.setFormatter(formatter)
53
- handler.setLevel(logging.NOTSET)