sf-veritas 0.9.7__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.
Potentially problematic release.
This version of sf-veritas might be problematic. Click here for more details.
- sf_veritas-0.9.7/PKG-INFO +83 -0
- sf_veritas-0.9.7/README.md +56 -0
- sf_veritas-0.9.7/pyproject.toml +33 -0
- sf_veritas-0.9.7/sf_veritas/.gitignore +2 -0
- sf_veritas-0.9.7/sf_veritas/__init__.py +4 -0
- sf_veritas-0.9.7/sf_veritas/app_config.py +49 -0
- sf_veritas-0.9.7/sf_veritas/cli.py +336 -0
- sf_veritas-0.9.7/sf_veritas/constants.py +3 -0
- sf_veritas-0.9.7/sf_veritas/custom_excepthook.py +285 -0
- sf_veritas-0.9.7/sf_veritas/custom_log_handler.py +53 -0
- sf_veritas-0.9.7/sf_veritas/custom_output_wrapper.py +107 -0
- sf_veritas-0.9.7/sf_veritas/custom_print.py +34 -0
- sf_veritas-0.9.7/sf_veritas/django_app.py +5 -0
- sf_veritas-0.9.7/sf_veritas/env_vars.py +83 -0
- sf_veritas-0.9.7/sf_veritas/exception_handling_middleware.py +18 -0
- sf_veritas-0.9.7/sf_veritas/exception_metaclass.py +69 -0
- sf_veritas-0.9.7/sf_veritas/frame_tools.py +112 -0
- sf_veritas-0.9.7/sf_veritas/import_hook.py +62 -0
- sf_veritas-0.9.7/sf_veritas/infra_details/__init__.py +3 -0
- sf_veritas-0.9.7/sf_veritas/infra_details/get_infra_details.py +24 -0
- sf_veritas-0.9.7/sf_veritas/infra_details/kubernetes/__init__.py +3 -0
- sf_veritas-0.9.7/sf_veritas/infra_details/kubernetes/get_cluster_name.py +147 -0
- sf_veritas-0.9.7/sf_veritas/infra_details/kubernetes/get_details.py +7 -0
- sf_veritas-0.9.7/sf_veritas/infra_details/running_on/__init__.py +17 -0
- sf_veritas-0.9.7/sf_veritas/infra_details/running_on/kubernetes.py +11 -0
- sf_veritas-0.9.7/sf_veritas/interceptors.py +252 -0
- sf_veritas-0.9.7/sf_veritas/local_env_detect.py +118 -0
- sf_veritas-0.9.7/sf_veritas/package_metadata.py +6 -0
- sf_veritas-0.9.7/sf_veritas/patches/__init__.py +0 -0
- sf_veritas-0.9.7/sf_veritas/patches/concurrent_futures.py +19 -0
- sf_veritas-0.9.7/sf_veritas/patches/constants.py +1 -0
- sf_veritas-0.9.7/sf_veritas/patches/exceptions.py +82 -0
- sf_veritas-0.9.7/sf_veritas/patches/multiprocessing.py +32 -0
- sf_veritas-0.9.7/sf_veritas/patches/network_libraries/__init__.py +51 -0
- sf_veritas-0.9.7/sf_veritas/patches/network_libraries/aiohttp.py +100 -0
- sf_veritas-0.9.7/sf_veritas/patches/network_libraries/curl_cffi.py +93 -0
- sf_veritas-0.9.7/sf_veritas/patches/network_libraries/http_client.py +64 -0
- sf_veritas-0.9.7/sf_veritas/patches/network_libraries/httpcore.py +152 -0
- sf_veritas-0.9.7/sf_veritas/patches/network_libraries/httplib2.py +76 -0
- sf_veritas-0.9.7/sf_veritas/patches/network_libraries/httpx.py +123 -0
- sf_veritas-0.9.7/sf_veritas/patches/network_libraries/niquests.py +192 -0
- sf_veritas-0.9.7/sf_veritas/patches/network_libraries/pycurl.py +71 -0
- sf_veritas-0.9.7/sf_veritas/patches/network_libraries/requests.py +187 -0
- sf_veritas-0.9.7/sf_veritas/patches/network_libraries/tornado.py +139 -0
- sf_veritas-0.9.7/sf_veritas/patches/network_libraries/treq.py +122 -0
- sf_veritas-0.9.7/sf_veritas/patches/network_libraries/urllib_request.py +129 -0
- sf_veritas-0.9.7/sf_veritas/patches/network_libraries/utils.py +101 -0
- sf_veritas-0.9.7/sf_veritas/patches/os.py +17 -0
- sf_veritas-0.9.7/sf_veritas/patches/threading.py +32 -0
- sf_veritas-0.9.7/sf_veritas/patches/web_frameworks/__init__.py +45 -0
- sf_veritas-0.9.7/sf_veritas/patches/web_frameworks/aiohttp.py +133 -0
- sf_veritas-0.9.7/sf_veritas/patches/web_frameworks/async_websocket_consumer.py +132 -0
- sf_veritas-0.9.7/sf_veritas/patches/web_frameworks/blacksheep.py +107 -0
- sf_veritas-0.9.7/sf_veritas/patches/web_frameworks/bottle.py +142 -0
- sf_veritas-0.9.7/sf_veritas/patches/web_frameworks/cherrypy.py +246 -0
- sf_veritas-0.9.7/sf_veritas/patches/web_frameworks/django.py +307 -0
- sf_veritas-0.9.7/sf_veritas/patches/web_frameworks/eve.py +138 -0
- sf_veritas-0.9.7/sf_veritas/patches/web_frameworks/falcon.py +229 -0
- sf_veritas-0.9.7/sf_veritas/patches/web_frameworks/fastapi.py +145 -0
- sf_veritas-0.9.7/sf_veritas/patches/web_frameworks/flask.py +186 -0
- sf_veritas-0.9.7/sf_veritas/patches/web_frameworks/klein.py +40 -0
- sf_veritas-0.9.7/sf_veritas/patches/web_frameworks/litestar.py +217 -0
- sf_veritas-0.9.7/sf_veritas/patches/web_frameworks/pyramid.py +89 -0
- sf_veritas-0.9.7/sf_veritas/patches/web_frameworks/quart.py +155 -0
- sf_veritas-0.9.7/sf_veritas/patches/web_frameworks/robyn.py +114 -0
- sf_veritas-0.9.7/sf_veritas/patches/web_frameworks/sanic.py +120 -0
- sf_veritas-0.9.7/sf_veritas/patches/web_frameworks/starlette.py +144 -0
- sf_veritas-0.9.7/sf_veritas/patches/web_frameworks/strawberry.py +269 -0
- sf_veritas-0.9.7/sf_veritas/patches/web_frameworks/tornado.py +129 -0
- sf_veritas-0.9.7/sf_veritas/patches/web_frameworks/utils.py +55 -0
- sf_veritas-0.9.7/sf_veritas/print_override.py +13 -0
- sf_veritas-0.9.7/sf_veritas/regular_data_transmitter.py +358 -0
- sf_veritas-0.9.7/sf_veritas/request_interceptor.py +399 -0
- sf_veritas-0.9.7/sf_veritas/request_utils.py +104 -0
- sf_veritas-0.9.7/sf_veritas/server_status.py +1 -0
- sf_veritas-0.9.7/sf_veritas/shutdown_flag.py +11 -0
- sf_veritas-0.9.7/sf_veritas/subprocess_startup.py +3 -0
- sf_veritas-0.9.7/sf_veritas/test_cli.py +145 -0
- sf_veritas-0.9.7/sf_veritas/thread_local.py +436 -0
- sf_veritas-0.9.7/sf_veritas/timeutil.py +114 -0
- sf_veritas-0.9.7/sf_veritas/transmit_exception_to_sailfish.py +28 -0
- sf_veritas-0.9.7/sf_veritas/transmitter.py +58 -0
- sf_veritas-0.9.7/sf_veritas/types.py +44 -0
- sf_veritas-0.9.7/sf_veritas/unified_interceptor.py +323 -0
- sf_veritas-0.9.7/sf_veritas/utils.py +39 -0
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: sf-veritas
|
|
3
|
+
Version: 0.9.7
|
|
4
|
+
Summary: Middleware for Django, Flask, and FastAPI to intercept requests, logs, and exceptions.
|
|
5
|
+
Keywords: publishToPublic
|
|
6
|
+
Author: Eric Meadows
|
|
7
|
+
Author-email: em@sailfishqa.com
|
|
8
|
+
Requires-Python: >=3.8,<4.0
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Provides-Extra: django
|
|
16
|
+
Provides-Extra: fastapi
|
|
17
|
+
Provides-Extra: flask
|
|
18
|
+
Requires-Dist: deepmerge (>=1.1.1,<2.0.0)
|
|
19
|
+
Requires-Dist: fire (>=0.7.0,<0.8.0)
|
|
20
|
+
Requires-Dist: ntplib (>=0.4.0,<0.5.0)
|
|
21
|
+
Requires-Dist: pydantic (>=2.9.2,<3.0.0)
|
|
22
|
+
Requires-Dist: python-dateutil (>=2.9.0.post0,<3.0.0)
|
|
23
|
+
Requires-Dist: requests (>=2.25.1,<3.0.0)
|
|
24
|
+
Requires-Dist: tldextract (>=5.1.2,<6.0.0)
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
|
|
27
|
+
# SF3 Middleware - Currently transmissions are diabled
|
|
28
|
+
|
|
29
|
+
<!-- TODO - turn on thread transmissions -->
|
|
30
|
+
|
|
31
|
+
Middleware for Python (Django, Flask, and FastAPI) to intercept requests, print statements, logs, and exceptions while persisting tracing.
|
|
32
|
+
|
|
33
|
+
## Installation
|
|
34
|
+
|
|
35
|
+
Use Poetry to install:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
poetry add sf-veritas
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Usage
|
|
42
|
+
|
|
43
|
+
### Django, etc
|
|
44
|
+
|
|
45
|
+
**Switch the following:**
|
|
46
|
+
|
|
47
|
+
```sh
|
|
48
|
+
python manage.py runserver 0.0.0.0:8000
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
**To:**
|
|
52
|
+
|
|
53
|
+
```sh
|
|
54
|
+
sf-veritas API-KEY python manage.py runserver 0.0.0.0:8000
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## TODO - Rename all Sailfish artifacts to Sailfish.ai
|
|
58
|
+
|
|
59
|
+
## Network Hop Calculation Time
|
|
60
|
+
|
|
61
|
+
To evaluate the performance impact of this package, we benchmarked 1000 HTTP requests with and without the package enabled.
|
|
62
|
+
|
|
63
|
+
| Configuration | Mean (ms) | Median (ms) | Std Dev (ms) |
|
|
64
|
+
|-------------------|-----------|-------------|--------------|
|
|
65
|
+
| ✅ With Package | 79.12 | 54.00 | 111.18 |
|
|
66
|
+
| ❌ Without Package | 69.70 | 52.00 | 73.78 |
|
|
67
|
+
|
|
68
|
+
> ⚠️ Note: The package introduces a slight increase in mean response time and variance. This trade-off may be acceptable depending on the value the package provides (e.g., additional logging, monitoring, or security features).
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## Optimized Entrypoint Capture (Post-Refactor)
|
|
73
|
+
|
|
74
|
+
After optimizing how the user-code entrypoint is captured (via faster stack inspection), we observed improved stability and performance across 1015 analyzed requests:
|
|
75
|
+
|
|
76
|
+
| Configuration | Mean (ms) | Median (ms) | Std Dev (ms) |
|
|
77
|
+
|-------------------|-----------|-------------|--------------|
|
|
78
|
+
| ✅ With Package | 142.45 | 138.50 | 80.78 |
|
|
79
|
+
| ❌ Without Package | 131.07 | 127.00 | 35.75 |
|
|
80
|
+
|
|
81
|
+
> ⚠️ The optimized implementation added a slight increase in mean latency (~8.7%), but this tradeoff is offset by improved accuracy of entrypoint capture.
|
|
82
|
+
---
|
|
83
|
+
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# SF3 Middleware - Currently transmissions are diabled
|
|
2
|
+
|
|
3
|
+
<!-- TODO - turn on thread transmissions -->
|
|
4
|
+
|
|
5
|
+
Middleware for Python (Django, Flask, and FastAPI) to intercept requests, print statements, logs, and exceptions while persisting tracing.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
Use Poetry to install:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
poetry add sf-veritas
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
### Django, etc
|
|
18
|
+
|
|
19
|
+
**Switch the following:**
|
|
20
|
+
|
|
21
|
+
```sh
|
|
22
|
+
python manage.py runserver 0.0.0.0:8000
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
**To:**
|
|
26
|
+
|
|
27
|
+
```sh
|
|
28
|
+
sf-veritas API-KEY python manage.py runserver 0.0.0.0:8000
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## TODO - Rename all Sailfish artifacts to Sailfish.ai
|
|
32
|
+
|
|
33
|
+
## Network Hop Calculation Time
|
|
34
|
+
|
|
35
|
+
To evaluate the performance impact of this package, we benchmarked 1000 HTTP requests with and without the package enabled.
|
|
36
|
+
|
|
37
|
+
| Configuration | Mean (ms) | Median (ms) | Std Dev (ms) |
|
|
38
|
+
|-------------------|-----------|-------------|--------------|
|
|
39
|
+
| ✅ With Package | 79.12 | 54.00 | 111.18 |
|
|
40
|
+
| ❌ Without Package | 69.70 | 52.00 | 73.78 |
|
|
41
|
+
|
|
42
|
+
> ⚠️ Note: The package introduces a slight increase in mean response time and variance. This trade-off may be acceptable depending on the value the package provides (e.g., additional logging, monitoring, or security features).
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Optimized Entrypoint Capture (Post-Refactor)
|
|
47
|
+
|
|
48
|
+
After optimizing how the user-code entrypoint is captured (via faster stack inspection), we observed improved stability and performance across 1015 analyzed requests:
|
|
49
|
+
|
|
50
|
+
| Configuration | Mean (ms) | Median (ms) | Std Dev (ms) |
|
|
51
|
+
|-------------------|-----------|-------------|--------------|
|
|
52
|
+
| ✅ With Package | 142.45 | 138.50 | 80.78 |
|
|
53
|
+
| ❌ Without Package | 131.07 | 127.00 | 35.75 |
|
|
54
|
+
|
|
55
|
+
> ⚠️ The optimized implementation added a slight increase in mean latency (~8.7%), but this tradeoff is offset by improved accuracy of entrypoint capture.
|
|
56
|
+
---
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
[tool.poetry]
|
|
2
|
+
name = "sf-veritas"
|
|
3
|
+
version = "0.9.7"
|
|
4
|
+
description = "Middleware for Django, Flask, and FastAPI to intercept requests, logs, and exceptions."
|
|
5
|
+
authors = ["Eric Meadows <em@sailfishqa.com>"]
|
|
6
|
+
readme = "README.md"
|
|
7
|
+
keywords = ["publishToPublic"]
|
|
8
|
+
|
|
9
|
+
[tool.poetry.dependencies]
|
|
10
|
+
python = "^3.8"
|
|
11
|
+
|
|
12
|
+
deepmerge = "^1.1.1"
|
|
13
|
+
fire = "^0.7.0"
|
|
14
|
+
ntplib = "^0.4.0"
|
|
15
|
+
pydantic = "^2.9.2"
|
|
16
|
+
requests = "^2.25.1"
|
|
17
|
+
tldextract = "^5.1.2"
|
|
18
|
+
python-dateutil = "^2.9.0.post0"
|
|
19
|
+
|
|
20
|
+
[tool.poetry.extras]
|
|
21
|
+
django = ["django"]
|
|
22
|
+
flask = ["flask"]
|
|
23
|
+
fastapi = ["fastapi"]
|
|
24
|
+
|
|
25
|
+
[tool.poetry.dev-dependencies]
|
|
26
|
+
pytest = "^6.2.4"
|
|
27
|
+
|
|
28
|
+
[tool.poetry.scripts]
|
|
29
|
+
sf-veritas = "sf_veritas.cli:fire_main"
|
|
30
|
+
|
|
31
|
+
[build-system]
|
|
32
|
+
requires = ["poetry-core>=1.0.0"]
|
|
33
|
+
build-backend = "poetry.core.masonry.api"
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import uuid
|
|
3
|
+
from typing import Dict, List, Optional, Union
|
|
4
|
+
|
|
5
|
+
from .constants import SAILFISH_DEFAULT_GRAPHQL_ENDPOINT
|
|
6
|
+
from .infra_details import get_infra_details
|
|
7
|
+
|
|
8
|
+
_service_uuid = str(uuid.uuid4())
|
|
9
|
+
_service_identifier = None
|
|
10
|
+
_service_version = None
|
|
11
|
+
_git_sha = None
|
|
12
|
+
_service_identification_received = False
|
|
13
|
+
_service_additional_metadata: Optional[Dict[str, Union[str, int, float, None]]] = None
|
|
14
|
+
|
|
15
|
+
_setup_interceptors_call_filename = None
|
|
16
|
+
_setup_interceptors_call_lineno = None
|
|
17
|
+
|
|
18
|
+
_interceptors_initialized = False
|
|
19
|
+
|
|
20
|
+
_profiling_mode_enabled: bool = False
|
|
21
|
+
_profiling_max_depth: int = 5
|
|
22
|
+
|
|
23
|
+
_site_and_dist_packages_to_collect_local_variables_on: Optional[List[str]] = []
|
|
24
|
+
|
|
25
|
+
_sailfish_api_key = None
|
|
26
|
+
_sailfish_graphql_endpoint: str = os.getenv(
|
|
27
|
+
"SAILFISH_GRAPHQL_ENDPOINT", SAILFISH_DEFAULT_GRAPHQL_ENDPOINT
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
_infra_details = get_infra_details()
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _set_site_and_dist_packages_to_collect_local_variables_on(
|
|
34
|
+
site_and_dist_packages_to_collect_local_variables_on: Optional[List[str]] = None,
|
|
35
|
+
):
|
|
36
|
+
if site_and_dist_packages_to_collect_local_variables_on is None:
|
|
37
|
+
_site_and_dist_packages_to_collect_local_variables_on = []
|
|
38
|
+
site_and_dist_packages_to_collect_local_variables_on_final: List[str] = (
|
|
39
|
+
site_and_dist_packages_to_collect_local_variables_on.copy()
|
|
40
|
+
)
|
|
41
|
+
for package in site_and_dist_packages_to_collect_local_variables_on:
|
|
42
|
+
if "-" not in package:
|
|
43
|
+
continue
|
|
44
|
+
site_and_dist_packages_to_collect_local_variables_on_final.append(
|
|
45
|
+
package.replace("-", "_")
|
|
46
|
+
)
|
|
47
|
+
_site_and_dist_packages_to_collect_local_variables_on = (
|
|
48
|
+
site_and_dist_packages_to_collect_local_variables_on_final
|
|
49
|
+
)
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import subprocess
|
|
3
|
+
import sys
|
|
4
|
+
import tempfile
|
|
5
|
+
from typing import Dict, List, Optional, Union
|
|
6
|
+
|
|
7
|
+
from fire import Fire
|
|
8
|
+
|
|
9
|
+
from .env_vars import SF_DEBUG
|
|
10
|
+
from .unified_interceptor import setup_interceptors
|
|
11
|
+
|
|
12
|
+
METHOD = "os.execvp"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def inject_code(file_path, **kwargs):
|
|
16
|
+
"""
|
|
17
|
+
Injects the given code snippet at the top of the target Python file.
|
|
18
|
+
"""
|
|
19
|
+
code_to_inject = f"""
|
|
20
|
+
from sf_veritas import setup_interceptors
|
|
21
|
+
|
|
22
|
+
setup_interceptors(
|
|
23
|
+
api_key={kwargs.get('api_key')!r},
|
|
24
|
+
service_identifier={kwargs.get('service_identifier', None)!r},
|
|
25
|
+
service_version={kwargs.get('service_version', None)!r},
|
|
26
|
+
service_additional_metadata={kwargs.get('service_additional_metadata', None)!r},
|
|
27
|
+
profiling_mode_enabled={kwargs.get('profiling_mode_enabled', False)},
|
|
28
|
+
profiling_max_depth={kwargs.get('profiling_max_depth', 5)},
|
|
29
|
+
domains_to_not_propagate_headers_to={kwargs.get('domains_to_not_propagate_headers_to', None)!r},
|
|
30
|
+
site_and_dist_packages_to_collect_local_variables_on={kwargs.get('site_and_dist_packages_to_collect_local_variables_on', None)!r}
|
|
31
|
+
)
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
# Read the original file content
|
|
35
|
+
with open(file_path, "r") as f:
|
|
36
|
+
original_content = f.read()
|
|
37
|
+
|
|
38
|
+
# Combine the injected code with the original content
|
|
39
|
+
new_content = code_to_inject + "\n" + original_content
|
|
40
|
+
|
|
41
|
+
# Create a temporary file in the same directory as the original script
|
|
42
|
+
temp_dir = os.path.dirname(file_path)
|
|
43
|
+
temp_file = tempfile.NamedTemporaryFile(
|
|
44
|
+
delete=False, dir=temp_dir, prefix="temp-", suffix=".py"
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
# Write the new content to the temporary file
|
|
48
|
+
with open(temp_file.name, "w") as f:
|
|
49
|
+
f.write(new_content)
|
|
50
|
+
|
|
51
|
+
return temp_file.name
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def get_sf_veritas_index(argv=sys.argv):
|
|
55
|
+
for i, arg in enumerate(argv):
|
|
56
|
+
if "sf-veritas" in arg:
|
|
57
|
+
return i
|
|
58
|
+
return None
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def get_python_index(argv=sys.argv):
|
|
62
|
+
if "python" in argv:
|
|
63
|
+
return argv.index("python")
|
|
64
|
+
if "python3" in argv:
|
|
65
|
+
return argv.index("python3")
|
|
66
|
+
return None
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def get_command_index(command_name, argv=sys.argv):
|
|
70
|
+
"""
|
|
71
|
+
Returns the index of a given command in sys.argv or None if not found.
|
|
72
|
+
"""
|
|
73
|
+
if command_name in argv:
|
|
74
|
+
return argv.index(command_name)
|
|
75
|
+
return None
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def handle_framework_command(
|
|
79
|
+
command_index,
|
|
80
|
+
module_delimiter=":",
|
|
81
|
+
argv=sys.argv,
|
|
82
|
+
app_path_offset: int = 1,
|
|
83
|
+
**kwargs,
|
|
84
|
+
):
|
|
85
|
+
"""
|
|
86
|
+
Handles framework commands like uvicorn, gunicorn, sanic, waitress-serve, daphne, etc.
|
|
87
|
+
"""
|
|
88
|
+
app_path = argv[command_index + app_path_offset]
|
|
89
|
+
module_path = app_path.split(module_delimiter)[0].replace(".", "/") + ".py"
|
|
90
|
+
|
|
91
|
+
# Inject code into the module
|
|
92
|
+
modified_module_path = inject_code(module_path, **kwargs)
|
|
93
|
+
|
|
94
|
+
# Turn the modified file path into a dotted module path
|
|
95
|
+
modified_module_relpath = os.path.relpath(modified_module_path, start=os.getcwd())
|
|
96
|
+
new_module_path = os.path.splitext(modified_module_relpath)[0].replace("/", ".")
|
|
97
|
+
|
|
98
|
+
# Strip off any leading dots that appear because of ../ or ./ references
|
|
99
|
+
new_module_path = new_module_path.lstrip(".")
|
|
100
|
+
|
|
101
|
+
# If there's still nothing, you could do a sanity check here:
|
|
102
|
+
# if not new_module_path:
|
|
103
|
+
# raise ValueError("Unable to determine a valid module name from path")
|
|
104
|
+
|
|
105
|
+
# Reconstruct the final "module:app" string
|
|
106
|
+
if module_delimiter in app_path:
|
|
107
|
+
# e.g., "backend.temp-abcdef:application"
|
|
108
|
+
modified_app_path = (
|
|
109
|
+
new_module_path + module_delimiter + app_path.split(module_delimiter)[1]
|
|
110
|
+
)
|
|
111
|
+
else:
|
|
112
|
+
modified_app_path = new_module_path
|
|
113
|
+
|
|
114
|
+
# Build the run_command
|
|
115
|
+
run_command = [argv[command_index], *argv[command_index + 1 :]]
|
|
116
|
+
run_command[app_path_offset] = modified_app_path
|
|
117
|
+
|
|
118
|
+
return run_command
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def find_application_arg(command_index, argv=sys.argv):
|
|
122
|
+
"""
|
|
123
|
+
Finds the application argument for daphne and granian commands, ignoring any flags.
|
|
124
|
+
"""
|
|
125
|
+
if SF_DEBUG:
|
|
126
|
+
print(
|
|
127
|
+
f"Checking arguments after index {command_index}: {argv[command_index + 1:]}",
|
|
128
|
+
log=False,
|
|
129
|
+
)
|
|
130
|
+
should_skip = False
|
|
131
|
+
for i in range(command_index + 1, len(argv)):
|
|
132
|
+
if should_skip:
|
|
133
|
+
should_skip = False
|
|
134
|
+
continue
|
|
135
|
+
if argv[i].startswith("-"):
|
|
136
|
+
should_skip = True
|
|
137
|
+
continue
|
|
138
|
+
return argv[i]
|
|
139
|
+
return None
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def main(
|
|
143
|
+
api_key: str,
|
|
144
|
+
service_identifier: str = None,
|
|
145
|
+
service_version: Union[str, int] = None, # Allow both string and int
|
|
146
|
+
service_additional_metadata: Dict[str, Union[str, int, float, None]] = None,
|
|
147
|
+
profiling_mode_enabled: bool = False,
|
|
148
|
+
profiling_max_depth: int = 5,
|
|
149
|
+
domains_to_not_propagate_headers_to: Optional[List[str]] = None,
|
|
150
|
+
site_and_dist_packages_to_collect_local_variables_on: Optional[List[str]] = None,
|
|
151
|
+
*args,
|
|
152
|
+
):
|
|
153
|
+
"""
|
|
154
|
+
Main function to handle CLI and set up interceptors.
|
|
155
|
+
|
|
156
|
+
Args:
|
|
157
|
+
api_key: (Required) API key for authentication. This must be provided as a CLI argument.
|
|
158
|
+
service_identifier: Identifier for the service.
|
|
159
|
+
service_version: Version of the service.
|
|
160
|
+
service_additional_metadata: Additional metadata to associate with the service.
|
|
161
|
+
profiling_mode_enabled: Whether profiling mode is enabled.
|
|
162
|
+
profiling_max_depth: Maximum depth for profiling.
|
|
163
|
+
domains_to_not_propagate_headers_to: Domains to which headers should not be propagated.
|
|
164
|
+
site_and_dist_packages_to_collect_local_variables_on: If set to None, defaults to [], excluding all installed packages.
|
|
165
|
+
*args: Additional command-line arguments to be passed through.
|
|
166
|
+
"""
|
|
167
|
+
|
|
168
|
+
# Convert service_version to string if it's numeric
|
|
169
|
+
if service_version is not None:
|
|
170
|
+
service_version = str(service_version)
|
|
171
|
+
|
|
172
|
+
# Collect setup parameters
|
|
173
|
+
setup_params = {
|
|
174
|
+
"api_key": api_key,
|
|
175
|
+
"service_identifier": service_identifier,
|
|
176
|
+
"service_version": service_version,
|
|
177
|
+
"service_additional_metadata": service_additional_metadata,
|
|
178
|
+
"profiling_mode_enabled": profiling_mode_enabled,
|
|
179
|
+
"profiling_max_depth": profiling_max_depth,
|
|
180
|
+
"domains_to_not_propagate_headers_to": domains_to_not_propagate_headers_to,
|
|
181
|
+
"site_and_dist_packages_to_collect_local_variables_on": site_and_dist_packages_to_collect_local_variables_on,
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
# Remaining arguments are captured in *args
|
|
185
|
+
remaining_args = sys.argv[1:]
|
|
186
|
+
try:
|
|
187
|
+
separator_index = sys.argv.index("--")
|
|
188
|
+
remaining_args = sys.argv[separator_index + 1 :]
|
|
189
|
+
except ValueError:
|
|
190
|
+
pass
|
|
191
|
+
|
|
192
|
+
if SF_DEBUG:
|
|
193
|
+
print("[[ DEBUG ]] sys.argv [[ /DEBUG ]]", sys.argv)
|
|
194
|
+
print("[[ DEBUG ]] setup_params [[ /DEBUG ]]", setup_params)
|
|
195
|
+
print("[[ DEBUG ]] remaining_args [[ /DEBUG ]]", remaining_args)
|
|
196
|
+
|
|
197
|
+
setup_interceptors(**setup_params)
|
|
198
|
+
|
|
199
|
+
if not remaining_args:
|
|
200
|
+
print("Usage: sf-veritas [options] <command> [args]")
|
|
201
|
+
sys.exit(1)
|
|
202
|
+
|
|
203
|
+
# Detect CLI command and adjust injection based on the framework
|
|
204
|
+
python_index = get_python_index(remaining_args)
|
|
205
|
+
uvicorn_index = get_command_index("uvicorn", remaining_args)
|
|
206
|
+
gunicorn_index = get_command_index("gunicorn", remaining_args)
|
|
207
|
+
flask_index = get_command_index("flask", remaining_args)
|
|
208
|
+
django_admin_index = get_command_index("django-admin", remaining_args)
|
|
209
|
+
sanic_index = get_command_index("sanic", remaining_args)
|
|
210
|
+
pserve_index = get_command_index("pserve", remaining_args)
|
|
211
|
+
waitress_index = get_command_index("waitress-serve", remaining_args)
|
|
212
|
+
daphne_index = get_command_index("daphne", remaining_args)
|
|
213
|
+
granian_index = get_command_index("granian", remaining_args)
|
|
214
|
+
|
|
215
|
+
if python_index is not None and python_index >= 0:
|
|
216
|
+
if "-m" in remaining_args:
|
|
217
|
+
module_index = remaining_args.index("-m", python_index)
|
|
218
|
+
|
|
219
|
+
# Ensure there's a module specified after `-m`
|
|
220
|
+
if module_index + 1 < len(remaining_args):
|
|
221
|
+
module_dot_path = remaining_args[module_index + 1]
|
|
222
|
+
module_path = module_dot_path.replace(".", "/") + ".py"
|
|
223
|
+
|
|
224
|
+
# Inject code into the module, passing setup_params
|
|
225
|
+
modified_module_path = inject_code(module_path, **setup_params)
|
|
226
|
+
|
|
227
|
+
# Calculate the relative path for the new temporary file's dot-path
|
|
228
|
+
new_module_dot_path = os.path.splitext(
|
|
229
|
+
os.path.relpath(modified_module_path, start=os.getcwd())
|
|
230
|
+
)[0].replace("/", ".")
|
|
231
|
+
|
|
232
|
+
# Rebuild the command with the modified module dot-path
|
|
233
|
+
code_after_module_index = module_index + 2
|
|
234
|
+
run_command = [
|
|
235
|
+
sys.executable,
|
|
236
|
+
"-m",
|
|
237
|
+
new_module_dot_path,
|
|
238
|
+
] + remaining_args[code_after_module_index:]
|
|
239
|
+
else:
|
|
240
|
+
print("Error: No module specified after '-m'.")
|
|
241
|
+
sys.exit(1)
|
|
242
|
+
else:
|
|
243
|
+
script_path = remaining_args[python_index + 1]
|
|
244
|
+
|
|
245
|
+
# Inject code into the script file directly, passing setup_params
|
|
246
|
+
modified_script_path = inject_code(script_path, **setup_params)
|
|
247
|
+
|
|
248
|
+
# Rebuild the command to use the path to the modified script file
|
|
249
|
+
run_command = [sys.executable, modified_script_path] + remaining_args[
|
|
250
|
+
python_index + 2 :
|
|
251
|
+
]
|
|
252
|
+
elif uvicorn_index is not None:
|
|
253
|
+
run_command = handle_framework_command(
|
|
254
|
+
uvicorn_index, module_delimiter=".", argv=remaining_args, **setup_params
|
|
255
|
+
)
|
|
256
|
+
elif gunicorn_index is not None:
|
|
257
|
+
run_command = handle_framework_command(
|
|
258
|
+
gunicorn_index, argv=remaining_args, **setup_params
|
|
259
|
+
)
|
|
260
|
+
elif sanic_index is not None:
|
|
261
|
+
run_command = handle_framework_command(
|
|
262
|
+
sanic_index, module_delimiter=".", argv=remaining_args, **setup_params
|
|
263
|
+
)
|
|
264
|
+
elif waitress_index is not None:
|
|
265
|
+
run_command = handle_framework_command(
|
|
266
|
+
waitress_index, module_delimiter=":", argv=remaining_args, **setup_params
|
|
267
|
+
)
|
|
268
|
+
elif flask_index is not None:
|
|
269
|
+
# Flask expects the FLASK_APP environment variable
|
|
270
|
+
app_path = os.environ.get("FLASK_APP", None)
|
|
271
|
+
if app_path:
|
|
272
|
+
module_path = app_path.replace(".", "/") + ".py"
|
|
273
|
+
|
|
274
|
+
# Inject code into the module, passing setup_params
|
|
275
|
+
modified_module_path = inject_code(module_path, **setup_params)
|
|
276
|
+
|
|
277
|
+
# Set the modified path back to FLASK_APP
|
|
278
|
+
os.environ["FLASK_APP"] = modified_module_path.replace("/", ".").replace(
|
|
279
|
+
".py", ""
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
run_command = remaining_args
|
|
283
|
+
elif django_admin_index is not None:
|
|
284
|
+
# Handle django-admin commands
|
|
285
|
+
script_path = remaining_args[django_admin_index]
|
|
286
|
+
|
|
287
|
+
# Inject code into the script file directly, passing setup_params
|
|
288
|
+
modified_script_path = inject_code(script_path, **setup_params)
|
|
289
|
+
|
|
290
|
+
# Rebuild the command to use the path to the modified script file
|
|
291
|
+
run_command = (
|
|
292
|
+
remaining_args[:django_admin_index]
|
|
293
|
+
+ [modified_script_path]
|
|
294
|
+
+ remaining_args[django_admin_index + 1 :]
|
|
295
|
+
)
|
|
296
|
+
elif daphne_index is not None or granian_index is not None:
|
|
297
|
+
command_index = daphne_index if daphne_index is not None else granian_index
|
|
298
|
+
app_arg = find_application_arg(command_index, argv=remaining_args)
|
|
299
|
+
app_path_offset = remaining_args.index(app_arg)
|
|
300
|
+
|
|
301
|
+
if not app_arg:
|
|
302
|
+
print("Error: No application module specified for daphne or granian.")
|
|
303
|
+
sys.exit(1)
|
|
304
|
+
|
|
305
|
+
run_command = handle_framework_command(
|
|
306
|
+
command_index,
|
|
307
|
+
module_delimiter=":",
|
|
308
|
+
argv=remaining_args,
|
|
309
|
+
app_path_offset=app_path_offset,
|
|
310
|
+
**setup_params,
|
|
311
|
+
)
|
|
312
|
+
elif pserve_index is not None:
|
|
313
|
+
# Rebuild the command to use pserve
|
|
314
|
+
run_command = remaining_args
|
|
315
|
+
else:
|
|
316
|
+
# For all other cases, pass the command as-is
|
|
317
|
+
run_command = remaining_args
|
|
318
|
+
|
|
319
|
+
if SF_DEBUG:
|
|
320
|
+
print("Run command is now:", run_command, log=False)
|
|
321
|
+
|
|
322
|
+
env = os.environ.copy()
|
|
323
|
+
|
|
324
|
+
if METHOD == "subprocess.run":
|
|
325
|
+
result = subprocess.run(run_command, env=env)
|
|
326
|
+
sys.exit(result.returncode)
|
|
327
|
+
else:
|
|
328
|
+
os.execvpe(run_command[0], run_command, env)
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
def fire_main():
|
|
332
|
+
return Fire(main)
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
if __name__ == "__main__":
|
|
336
|
+
fire_main()
|