You've already forked mariadb-columnstore-engine
mirror of
https://github.com/mariadb-corporation/mariadb-columnstore-engine.git
synced 2025-11-03 17:13:17 +03:00
Added support for Sentry in cmapi server
Support distributed request tracing -Direct dependencies now in requirements[-dev].in, pip-compile generates full requirement[-dev].txt from them
This commit is contained in:
@@ -2,13 +2,13 @@
|
||||
[](https://ci.columnstore.mariadb.net/mariadb-corporation/mariadb-columnstore-cmapi)
|
||||
|
||||
## Overview
|
||||
This RESTfull server enables multi-node setups for MCS.
|
||||
This RESTful server enables multi-node setups for MCS.
|
||||
|
||||
## Requirements
|
||||
|
||||
See requirements.txt file.
|
||||
|
||||
All the Python packages prerequisits are shipped with a pre-built Python enterpreter.
|
||||
All the Python packages prerequisites are shipped with a pre-built Python interpreter.
|
||||
|
||||
## Usage
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ from cherrypy.process import plugins
|
||||
# TODO: fix dispatcher choose logic because code executing in endpoints.py
|
||||
# while import process, this cause module logger misconfiguration
|
||||
from cmapi_server.logging_management import config_cmapi_server_logging
|
||||
from cmapi_server.sentry import maybe_init_sentry, register_sentry_cherrypy_tool
|
||||
config_cmapi_server_logging()
|
||||
|
||||
from cmapi_server import helpers
|
||||
@@ -140,15 +141,24 @@ if __name__ == '__main__':
|
||||
# TODO: read cmapi config filepath as an argument
|
||||
helpers.cmapi_config_check()
|
||||
|
||||
# Init Sentry if DSN is present
|
||||
sentry_active = maybe_init_sentry()
|
||||
if sentry_active:
|
||||
register_sentry_cherrypy_tool()
|
||||
|
||||
CertificateManager.create_self_signed_certificate_if_not_exist()
|
||||
CertificateManager.renew_certificate()
|
||||
|
||||
app = cherrypy.tree.mount(root=None, config=CMAPI_CONF_PATH)
|
||||
root_config = {
|
||||
"request.dispatch": dispatcher,
|
||||
"error_page.default": jsonify_error,
|
||||
}
|
||||
if sentry_active:
|
||||
root_config["tools.sentry.on"] = True
|
||||
|
||||
app.config.update({
|
||||
'/': {
|
||||
'request.dispatch': dispatcher,
|
||||
'error_page.default': jsonify_error,
|
||||
},
|
||||
'/': root_config,
|
||||
'config': {
|
||||
'path': CMAPI_CONF_PATH,
|
||||
},
|
||||
|
||||
197
cmapi/cmapi_server/sentry.py
Normal file
197
cmapi/cmapi_server/sentry.py
Normal file
@@ -0,0 +1,197 @@
|
||||
import logging
|
||||
import socket
|
||||
|
||||
import cherrypy
|
||||
import sentry_sdk
|
||||
from sentry_sdk.integrations.aiohttp import AioHttpIntegration
|
||||
from sentry_sdk.integrations.logging import LoggingIntegration
|
||||
|
||||
from cmapi_server import helpers
|
||||
from cmapi_server.constants import CMAPI_CONF_PATH
|
||||
|
||||
SENTRY_ACTIVE = False
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def maybe_init_sentry() -> bool:
|
||||
"""Initialize Sentry from CMAPI configuration.
|
||||
|
||||
Reads config and initializes Sentry only if dsn parameter is present in corresponding section.
|
||||
The initialization enables the following integrations:
|
||||
- LoggingIntegration: capture warning-level logs as Sentry events and use
|
||||
lower-level logs as breadcrumbs.
|
||||
- AioHttpIntegration: propagate trace headers for outbound requests made
|
||||
with `aiohttp`.
|
||||
|
||||
The function is a no-op if the DSN is missing.
|
||||
|
||||
Returns: True if Sentry is initialized, False otherwise.
|
||||
"""
|
||||
global SENTRY_ACTIVE
|
||||
try:
|
||||
cfg_parser = helpers.get_config_parser(CMAPI_CONF_PATH)
|
||||
dsn = helpers.dequote(
|
||||
cfg_parser.get('Sentry', 'dsn', fallback='').strip()
|
||||
)
|
||||
if not dsn:
|
||||
return False
|
||||
|
||||
environment = helpers.dequote(
|
||||
cfg_parser.get('Sentry', 'environment', fallback='development').strip()
|
||||
)
|
||||
traces_sample_rate_str = helpers.dequote(
|
||||
cfg_parser.get('Sentry', 'traces_sample_rate', fallback='1.0').strip()
|
||||
)
|
||||
except Exception:
|
||||
logger.exception('Failed to initialize Sentry.')
|
||||
return False
|
||||
|
||||
try:
|
||||
sentry_logging = LoggingIntegration(
|
||||
level=logging.INFO,
|
||||
event_level=logging.WARNING,
|
||||
)
|
||||
|
||||
try:
|
||||
traces_sample_rate = float(traces_sample_rate_str)
|
||||
except ValueError:
|
||||
logger.error('Invalid traces_sample_rate: %s', traces_sample_rate_str)
|
||||
traces_sample_rate = 1.0
|
||||
|
||||
sentry_sdk.init(
|
||||
dsn=dsn,
|
||||
environment=environment,
|
||||
traces_sample_rate=traces_sample_rate,
|
||||
integrations=[sentry_logging, AioHttpIntegration()],
|
||||
)
|
||||
SENTRY_ACTIVE = True
|
||||
logger.info('Sentry initialized for CMAPI via config.')
|
||||
except Exception:
|
||||
logger.exception('Failed to initialize Sentry.')
|
||||
return False
|
||||
|
||||
logger.info('Sentry successfully initialized.')
|
||||
return True
|
||||
|
||||
def _sentry_on_start_resource():
|
||||
"""Start or continue a Sentry transaction for the current CherryPy request.
|
||||
|
||||
- Continues an incoming distributed trace using Sentry trace headers if
|
||||
present; otherwise starts a new transaction with `op='http.server'`.
|
||||
- Pushes the transaction into the current Sentry scope and attaches useful
|
||||
request metadata as tags and context (HTTP method, path, client IP,
|
||||
hostname, request ID, and a filtered subset of headers).
|
||||
- Stores the transaction on the CherryPy request object for later finishing
|
||||
in `_sentry_on_end_request`.
|
||||
"""
|
||||
if not SENTRY_ACTIVE:
|
||||
return
|
||||
try:
|
||||
request = cherrypy.request
|
||||
headers = dict(getattr(request, 'headers', {}) or {})
|
||||
name = f"{request.method} {request.path_info}"
|
||||
transaction = sentry_sdk.start_transaction(
|
||||
op='http.server', name=name, continue_from_headers=headers
|
||||
)
|
||||
sentry_sdk.Hub.current.scope.set_span(transaction)
|
||||
|
||||
# Add request-level context/tags
|
||||
scope = sentry_sdk.Hub.current.scope
|
||||
scope.set_tag('http.method', request.method)
|
||||
scope.set_tag('http.path', request.path_info)
|
||||
scope.set_tag('client.ip', getattr(request.remote, 'ip', ''))
|
||||
scope.set_tag('instance.hostname', socket.gethostname())
|
||||
request_id = getattr(request, 'unique_id', None)
|
||||
if request_id:
|
||||
scope.set_tag('request.id', request_id)
|
||||
# Optionally add headers as context without sensitive values
|
||||
safe_headers = {k: v for k, v in headers.items()
|
||||
if k.lower() not in {'authorization', 'x-api-key'}}
|
||||
scope.set_context('headers', safe_headers)
|
||||
|
||||
request.sentry_transaction = transaction
|
||||
except Exception:
|
||||
logger.exception('Failed to start Sentry transaction.')
|
||||
|
||||
|
||||
def _sentry_before_error_response():
|
||||
"""Capture the current exception (if any) to Sentry before error response.
|
||||
|
||||
This hook runs when CherryPy prepares an error response. If an exception is
|
||||
available in the current context, it will be sent to Sentry.
|
||||
"""
|
||||
if not SENTRY_ACTIVE:
|
||||
return
|
||||
try:
|
||||
sentry_sdk.capture_exception()
|
||||
except Exception:
|
||||
logger.exception('Failed to capture exception to Sentry.')
|
||||
|
||||
|
||||
def _sentry_on_end_request():
|
||||
"""Finish the Sentry transaction for the current CherryPy request.
|
||||
|
||||
Attempts to set the HTTP status code on the active transaction and then
|
||||
finishes it. If no transaction was started on this request, the function is
|
||||
a no-op.
|
||||
"""
|
||||
if not SENTRY_ACTIVE:
|
||||
return
|
||||
try:
|
||||
request = cherrypy.request
|
||||
transaction = getattr(request, 'sentry_transaction', None)
|
||||
if transaction is None:
|
||||
return
|
||||
status = cherrypy.response.status
|
||||
try:
|
||||
status_code = int(str(status).split()[0])
|
||||
except Exception:
|
||||
status_code = None
|
||||
try:
|
||||
if status_code is not None and hasattr(transaction, 'set_http_status'):
|
||||
transaction.set_http_status(status_code)
|
||||
except Exception:
|
||||
logger.exception('Failed to set HTTP status code on Sentry transaction.')
|
||||
transaction.finish()
|
||||
except Exception:
|
||||
logger.exception('Failed to finish Sentry transaction.')
|
||||
|
||||
|
||||
class SentryTool(cherrypy.Tool):
|
||||
"""CherryPy Tool that wires Sentry request lifecycle hooks.
|
||||
|
||||
The tool attaches handlers for `on_start_resource`, `before_error_response`,
|
||||
and `on_end_request` in order to manage Sentry transactions and error
|
||||
capture across the request lifecycle.
|
||||
"""
|
||||
def __init__(self):
|
||||
cherrypy.Tool.__init__(self, 'on_start_resource', self._tool_callback, priority=50)
|
||||
|
||||
@staticmethod
|
||||
def _tool_callback():
|
||||
"""Attach Sentry lifecycle callbacks to the current CherryPy request."""
|
||||
cherrypy.request.hooks.attach(
|
||||
'on_start_resource', _sentry_on_start_resource, priority=50
|
||||
)
|
||||
cherrypy.request.hooks.attach(
|
||||
'before_error_response', _sentry_before_error_response, priority=60
|
||||
)
|
||||
cherrypy.request.hooks.attach(
|
||||
'on_end_request', _sentry_on_end_request, priority=70
|
||||
)
|
||||
|
||||
|
||||
def register_sentry_cherrypy_tool() -> None:
|
||||
"""Register the Sentry CherryPy tool under `tools.sentry`.
|
||||
|
||||
This function is safe to call multiple times; failures are silently ignored
|
||||
to avoid impacting the application startup.
|
||||
"""
|
||||
if not SENTRY_ACTIVE:
|
||||
return
|
||||
|
||||
try:
|
||||
cherrypy.tools.sentry = SentryTool()
|
||||
except Exception:
|
||||
logger.exception('Failed to register Sentry CherryPy tool.')
|
||||
|
||||
70
cmapi/dev_tools/piptools.sh
Executable file
70
cmapi/dev_tools/piptools.sh
Executable file
@@ -0,0 +1,70 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
cmapi_dir="$(realpath "${script_dir}/..")"
|
||||
|
||||
export CUSTOM_COMPILE_COMMAND="dev_tools/piptools.sh compile-all"
|
||||
|
||||
ensure_piptools() {
|
||||
if ! command -v pip-compile >/dev/null 2>&1; then
|
||||
echo "Installing pip-tools..."
|
||||
python3 -m pip install --upgrade pip
|
||||
python3 -m pip install pip-tools
|
||||
fi
|
||||
}
|
||||
|
||||
compile_runtime() {
|
||||
ensure_piptools
|
||||
cd "${cmapi_dir}"
|
||||
pip-compile --quiet --resolver=backtracking --output-file=requirements.txt requirements.in
|
||||
}
|
||||
|
||||
compile_dev() {
|
||||
ensure_piptools
|
||||
cd "${cmapi_dir}"
|
||||
pip-compile --quiet --resolver=backtracking --output-file=requirements-dev.txt requirements-dev.in
|
||||
}
|
||||
|
||||
compile_all() {
|
||||
compile_runtime
|
||||
compile_dev
|
||||
}
|
||||
|
||||
sync_runtime() {
|
||||
ensure_piptools
|
||||
cd "${cmapi_dir}"
|
||||
pip-sync requirements.txt
|
||||
}
|
||||
|
||||
sync_dev() {
|
||||
ensure_piptools
|
||||
cd "${cmapi_dir}"
|
||||
pip-sync requirements.txt requirements-dev.txt
|
||||
}
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
Usage: dev_tools/piptools.sh <command>
|
||||
|
||||
Commands:
|
||||
compile-runtime Compile requirements.in -> requirements.txt
|
||||
compile-dev Compile requirements-dev.in -> requirements-dev.txt
|
||||
compile-all Compile both runtime and dev requirements (default)
|
||||
sync-runtime pip-sync runtime requirements only
|
||||
sync-dev pip-sync runtime + dev requirements
|
||||
help Show this help
|
||||
EOF
|
||||
}
|
||||
|
||||
cmd="${1:-compile-all}"
|
||||
case "${cmd}" in
|
||||
compile-runtime) compile_runtime ;;
|
||||
compile-dev) compile_dev ;;
|
||||
compile-all) compile_all ;;
|
||||
sync-runtime) sync_runtime ;;
|
||||
sync-dev) sync_dev ;;
|
||||
help|--help|-h) usage ;;
|
||||
*) echo "Unknown command: ${cmd}" >&2; usage; exit 1 ;;
|
||||
esac
|
||||
|
||||
8
cmapi/requirements-dev.in
Normal file
8
cmapi/requirements-dev.in
Normal file
@@ -0,0 +1,8 @@
|
||||
# Direct, top-level development/testing dependencies
|
||||
# Compile with: pip-compile --output-file=requirements-dev.txt requirements-dev.in
|
||||
|
||||
pytest==8.3.5
|
||||
fabric==3.2.2
|
||||
# Tooling
|
||||
pip-tools
|
||||
|
||||
@@ -1,37 +1,69 @@
|
||||
# For integration tests
|
||||
pytest==8.3.5
|
||||
fabric==3.2.2
|
||||
|
||||
# This frozen part is autogenerated by pip-compile: pip-compile requirements-dev.txt
|
||||
#
|
||||
# This file is autogenerated by pip-compile with Python 3.9
|
||||
# by the following command:
|
||||
#
|
||||
# dev_tools/piptools.sh compile-all
|
||||
#
|
||||
bcrypt==4.3.0
|
||||
# via paramiko
|
||||
build==1.3.0
|
||||
# via pip-tools
|
||||
cffi==1.17.1
|
||||
# via
|
||||
# cryptography
|
||||
# pynacl
|
||||
click==8.1.8
|
||||
# via pip-tools
|
||||
cryptography==45.0.5
|
||||
# via paramiko
|
||||
decorator==5.2.1
|
||||
# via fabric
|
||||
deprecated==1.2.18
|
||||
# via fabric
|
||||
exceptiongroup==1.3.0
|
||||
# via pytest
|
||||
fabric==3.2.2
|
||||
# via -r requirements-dev.txt
|
||||
# via -r requirements-dev.in
|
||||
importlib-metadata==8.7.0
|
||||
# via build
|
||||
iniconfig==2.1.0
|
||||
# via pytest
|
||||
invoke==2.2.0
|
||||
# via fabric
|
||||
packaging==25.0
|
||||
# via pytest
|
||||
# via
|
||||
# build
|
||||
# pytest
|
||||
paramiko==3.5.1
|
||||
# via fabric
|
||||
pip-tools==7.5.0
|
||||
# via -r requirements-dev.in
|
||||
pluggy==1.6.0
|
||||
# via pytest
|
||||
pycparser==2.22
|
||||
# via cffi
|
||||
pynacl==1.5.0
|
||||
# via paramiko
|
||||
pyproject-hooks==1.2.0
|
||||
# via
|
||||
# build
|
||||
# pip-tools
|
||||
pytest==8.3.5
|
||||
# via -r requirements-dev.txt
|
||||
# via -r requirements-dev.in
|
||||
tomli==2.2.1
|
||||
# via
|
||||
# build
|
||||
# pip-tools
|
||||
# pytest
|
||||
typing-extensions==4.14.1
|
||||
# via exceptiongroup
|
||||
wheel==0.45.1
|
||||
# via pip-tools
|
||||
wrapt==1.17.2
|
||||
# via deprecated
|
||||
zipp==3.23.0
|
||||
# via importlib-metadata
|
||||
|
||||
# The following packages are considered to be unsafe in a requirements file:
|
||||
# pip
|
||||
# setuptools
|
||||
|
||||
19
cmapi/requirements.in
Normal file
19
cmapi/requirements.in
Normal file
@@ -0,0 +1,19 @@
|
||||
# Direct, top-level runtime dependencies for cmapi
|
||||
# Compile with: pip-compile --output-file=requirements.txt requirements.in
|
||||
|
||||
aiohttp==3.11.16
|
||||
awscli==1.38.28
|
||||
CherryPy==18.10.0
|
||||
cryptography==43.0.3
|
||||
furl==2.1.4
|
||||
gsutil==5.33
|
||||
lxml==5.3.2
|
||||
psutil==7.0.0
|
||||
pyotp==2.9.0
|
||||
requests==2.32.3
|
||||
# required for CherryPy RoutesDispatcher,
|
||||
# but CherryPy itself has no such dependency
|
||||
Routes==2.5.1
|
||||
typer==0.15.2
|
||||
sentry-sdk==2.34.1
|
||||
|
||||
@@ -1,78 +1,231 @@
|
||||
aiohttp==3.11.16
|
||||
awscli==1.38.28
|
||||
CherryPy==18.10.0
|
||||
cryptography==43.0.3
|
||||
furl==2.1.4
|
||||
gsutil==5.33
|
||||
lxml==5.3.2
|
||||
psutil==7.0.0
|
||||
pyotp==2.9.0
|
||||
requests==2.32.3
|
||||
# required for CherryPy RoutesDispatcher,
|
||||
# but CherryPy itself has no such a dependency
|
||||
Routes==2.5.1
|
||||
typer==0.15.2
|
||||
|
||||
# indirect dependencies
|
||||
#
|
||||
# This file is autogenerated by pip-compile with Python 3.9
|
||||
# by the following command:
|
||||
#
|
||||
# dev_tools/piptools.sh compile-all
|
||||
#
|
||||
aiohappyeyeballs==2.6.1
|
||||
# via aiohttp
|
||||
aiohttp==3.11.16
|
||||
# via
|
||||
# -r requirements.in
|
||||
# google-auth
|
||||
aiosignal==1.3.2
|
||||
# via aiohttp
|
||||
argcomplete==3.6.2
|
||||
# via gsutil
|
||||
async-timeout==5.0.1
|
||||
# via aiohttp
|
||||
attrs==25.3.0
|
||||
# via aiohttp
|
||||
autocommand==2.2.2
|
||||
backports.tarfile==1.2.0
|
||||
# via jaraco-text
|
||||
awscli==1.38.28
|
||||
# via -r requirements.in
|
||||
backports-tarfile==1.2.0
|
||||
# via jaraco-context
|
||||
boto==2.49.0
|
||||
# via gcs-oauth2-boto-plugin
|
||||
botocore==1.37.28
|
||||
# via
|
||||
# awscli
|
||||
# s3transfer
|
||||
cachetools==5.5.2
|
||||
# via google-auth
|
||||
certifi==2025.1.31
|
||||
# via
|
||||
# requests
|
||||
# sentry-sdk
|
||||
cffi==1.17.1
|
||||
# via cryptography
|
||||
charset-normalizer==3.4.1
|
||||
# via requests
|
||||
cheroot==10.0.1
|
||||
# via cherrypy
|
||||
cherrypy==18.10.0
|
||||
# via -r requirements.in
|
||||
click==8.1.8
|
||||
# via typer
|
||||
colorama==0.4.6
|
||||
# via awscli
|
||||
crcmod==1.7
|
||||
# via gsutil
|
||||
cryptography==43.0.3
|
||||
# via
|
||||
# -r requirements.in
|
||||
# pyopenssl
|
||||
docutils==0.16
|
||||
# via awscli
|
||||
fasteners==0.19
|
||||
# via
|
||||
# google-apitools
|
||||
# gsutil
|
||||
frozenlist==1.5.0
|
||||
# via
|
||||
# aiohttp
|
||||
# aiosignal
|
||||
furl==2.1.4
|
||||
# via -r requirements.in
|
||||
gcs-oauth2-boto-plugin==3.2
|
||||
# via gsutil
|
||||
google-apitools==0.5.32
|
||||
google-auth==2.17.0
|
||||
# via gsutil
|
||||
google-auth[aiohttp]==2.17.0
|
||||
# via
|
||||
# gcs-oauth2-boto-plugin
|
||||
# google-auth-httplib2
|
||||
# gsutil
|
||||
google-auth-httplib2==0.2.0
|
||||
# via
|
||||
# gcs-oauth2-boto-plugin
|
||||
# gsutil
|
||||
google-reauth==0.1.1
|
||||
# via
|
||||
# gcs-oauth2-boto-plugin
|
||||
# gsutil
|
||||
gsutil==5.33
|
||||
# via -r requirements.in
|
||||
httplib2==0.20.4
|
||||
# via
|
||||
# gcs-oauth2-boto-plugin
|
||||
# google-apitools
|
||||
# google-auth-httplib2
|
||||
# gsutil
|
||||
# oauth2client
|
||||
idna==3.10
|
||||
jaraco.collections==5.1.0
|
||||
jaraco.context==6.0.1
|
||||
jaraco.functools==4.1.0
|
||||
jaraco.text==4.0.0
|
||||
# via
|
||||
# requests
|
||||
# yarl
|
||||
jaraco-collections==5.1.0
|
||||
# via cherrypy
|
||||
jaraco-context==6.0.1
|
||||
# via jaraco-text
|
||||
jaraco-functools==4.1.0
|
||||
# via
|
||||
# cheroot
|
||||
# jaraco-text
|
||||
# tempora
|
||||
jaraco-text==4.0.0
|
||||
# via jaraco-collections
|
||||
jmespath==1.0.1
|
||||
# via botocore
|
||||
lxml==5.3.2
|
||||
# via -r requirements.in
|
||||
markdown-it-py==3.0.0
|
||||
# via rich
|
||||
mdurl==0.1.2
|
||||
# via markdown-it-py
|
||||
monotonic==1.6
|
||||
# via gsutil
|
||||
more-itertools==10.6.0
|
||||
# via
|
||||
# cheroot
|
||||
# cherrypy
|
||||
# jaraco-functools
|
||||
# jaraco-text
|
||||
multidict==6.3.2
|
||||
# via
|
||||
# aiohttp
|
||||
# yarl
|
||||
oauth2client==4.1.3
|
||||
# via
|
||||
# gcs-oauth2-boto-plugin
|
||||
# google-apitools
|
||||
orderedmultidict==1.0.1
|
||||
# via furl
|
||||
portend==3.2.0
|
||||
# via cherrypy
|
||||
propcache==0.3.1
|
||||
# via
|
||||
# aiohttp
|
||||
# yarl
|
||||
psutil==7.0.0
|
||||
# via -r requirements.in
|
||||
pyasn1==0.6.1
|
||||
pyasn1_modules==0.4.2
|
||||
# via
|
||||
# oauth2client
|
||||
# pyasn1-modules
|
||||
# rsa
|
||||
pyasn1-modules==0.4.2
|
||||
# via
|
||||
# google-auth
|
||||
# oauth2client
|
||||
pycparser==2.22
|
||||
Pygments==2.19.1
|
||||
pyOpenSSL==24.2.1
|
||||
# via cffi
|
||||
pygments==2.19.1
|
||||
# via rich
|
||||
pyopenssl==24.2.1
|
||||
# via
|
||||
# gcs-oauth2-boto-plugin
|
||||
# gsutil
|
||||
pyotp==2.9.0
|
||||
# via -r requirements.in
|
||||
pyparsing==3.2.3
|
||||
# via httplib2
|
||||
python-dateutil==2.9.0.post0
|
||||
# via
|
||||
# botocore
|
||||
# tempora
|
||||
pyu2f==0.1.5
|
||||
PyYAML==6.0.2
|
||||
repoze.lru==0.7
|
||||
# via google-reauth
|
||||
pyyaml==6.0.2
|
||||
# via awscli
|
||||
repoze-lru==0.7
|
||||
# via routes
|
||||
requests==2.32.3
|
||||
# via
|
||||
# -r requirements.in
|
||||
# google-auth
|
||||
retry-decorator==1.1.1
|
||||
# via
|
||||
# gcs-oauth2-boto-plugin
|
||||
# gsutil
|
||||
rich==14.0.0
|
||||
# via typer
|
||||
routes==2.5.1
|
||||
# via -r requirements.in
|
||||
rsa==4.7.2
|
||||
# via
|
||||
# awscli
|
||||
# gcs-oauth2-boto-plugin
|
||||
# google-auth
|
||||
# oauth2client
|
||||
s3transfer==0.11.4
|
||||
# via awscli
|
||||
sentry-sdk==2.34.1
|
||||
# via -r requirements.in
|
||||
shellingham==1.5.4
|
||||
# via typer
|
||||
six==1.17.0
|
||||
# via
|
||||
# furl
|
||||
# gcs-oauth2-boto-plugin
|
||||
# google-apitools
|
||||
# google-auth
|
||||
# gsutil
|
||||
# oauth2client
|
||||
# orderedmultidict
|
||||
# python-dateutil
|
||||
# pyu2f
|
||||
# routes
|
||||
tempora==5.8.0
|
||||
typing_extensions==4.13.1
|
||||
# via portend
|
||||
typer==0.15.2
|
||||
# via -r requirements.in
|
||||
typing-extensions==4.13.1
|
||||
# via
|
||||
# multidict
|
||||
# rich
|
||||
# typer
|
||||
urllib3==1.26.20
|
||||
# via
|
||||
# botocore
|
||||
# requests
|
||||
# sentry-sdk
|
||||
yarl==1.19.0
|
||||
zc.lockfile==3.0.post1
|
||||
# via aiohttp
|
||||
zc-lockfile==3.0.post1
|
||||
# via cherrypy
|
||||
|
||||
# The following packages are considered to be unsafe in a requirements file:
|
||||
# setuptools
|
||||
|
||||
Reference in New Issue
Block a user