provide-foundation 0.0.0.dev0__py3-none-any.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.
- provide/__init__.py +15 -0
- provide/foundation/__init__.py +155 -0
- provide/foundation/_version.py +58 -0
- provide/foundation/cli/__init__.py +67 -0
- provide/foundation/cli/commands/__init__.py +3 -0
- provide/foundation/cli/commands/deps.py +71 -0
- provide/foundation/cli/commands/logs/__init__.py +63 -0
- provide/foundation/cli/commands/logs/generate.py +357 -0
- provide/foundation/cli/commands/logs/generate_old.py +569 -0
- provide/foundation/cli/commands/logs/query.py +174 -0
- provide/foundation/cli/commands/logs/send.py +166 -0
- provide/foundation/cli/commands/logs/tail.py +112 -0
- provide/foundation/cli/decorators.py +262 -0
- provide/foundation/cli/main.py +65 -0
- provide/foundation/cli/testing.py +220 -0
- provide/foundation/cli/utils.py +210 -0
- provide/foundation/config/__init__.py +106 -0
- provide/foundation/config/base.py +295 -0
- provide/foundation/config/env.py +369 -0
- provide/foundation/config/loader.py +311 -0
- provide/foundation/config/manager.py +387 -0
- provide/foundation/config/schema.py +284 -0
- provide/foundation/config/sync.py +281 -0
- provide/foundation/config/types.py +78 -0
- provide/foundation/config/validators.py +80 -0
- provide/foundation/console/__init__.py +29 -0
- provide/foundation/console/input.py +364 -0
- provide/foundation/console/output.py +178 -0
- provide/foundation/context/__init__.py +12 -0
- provide/foundation/context/core.py +356 -0
- provide/foundation/core.py +20 -0
- provide/foundation/crypto/__init__.py +182 -0
- provide/foundation/crypto/algorithms.py +111 -0
- provide/foundation/crypto/certificates.py +896 -0
- provide/foundation/crypto/checksums.py +301 -0
- provide/foundation/crypto/constants.py +57 -0
- provide/foundation/crypto/hashing.py +265 -0
- provide/foundation/crypto/keys.py +188 -0
- provide/foundation/crypto/signatures.py +144 -0
- provide/foundation/crypto/utils.py +164 -0
- provide/foundation/errors/__init__.py +96 -0
- provide/foundation/errors/auth.py +73 -0
- provide/foundation/errors/base.py +81 -0
- provide/foundation/errors/config.py +103 -0
- provide/foundation/errors/context.py +299 -0
- provide/foundation/errors/decorators.py +484 -0
- provide/foundation/errors/handlers.py +360 -0
- provide/foundation/errors/integration.py +105 -0
- provide/foundation/errors/platform.py +37 -0
- provide/foundation/errors/process.py +140 -0
- provide/foundation/errors/resources.py +133 -0
- provide/foundation/errors/runtime.py +160 -0
- provide/foundation/errors/safe_decorators.py +133 -0
- provide/foundation/errors/types.py +276 -0
- provide/foundation/file/__init__.py +79 -0
- provide/foundation/file/atomic.py +157 -0
- provide/foundation/file/directory.py +134 -0
- provide/foundation/file/formats.py +236 -0
- provide/foundation/file/lock.py +175 -0
- provide/foundation/file/safe.py +179 -0
- provide/foundation/file/utils.py +170 -0
- provide/foundation/hub/__init__.py +88 -0
- provide/foundation/hub/click_builder.py +310 -0
- provide/foundation/hub/commands.py +42 -0
- provide/foundation/hub/components.py +640 -0
- provide/foundation/hub/decorators.py +244 -0
- provide/foundation/hub/info.py +32 -0
- provide/foundation/hub/manager.py +446 -0
- provide/foundation/hub/registry.py +279 -0
- provide/foundation/hub/type_mapping.py +54 -0
- provide/foundation/hub/types.py +28 -0
- provide/foundation/logger/__init__.py +41 -0
- provide/foundation/logger/base.py +22 -0
- provide/foundation/logger/config/__init__.py +16 -0
- provide/foundation/logger/config/base.py +40 -0
- provide/foundation/logger/config/logging.py +394 -0
- provide/foundation/logger/config/telemetry.py +188 -0
- provide/foundation/logger/core.py +239 -0
- provide/foundation/logger/custom_processors.py +172 -0
- provide/foundation/logger/emoji/__init__.py +44 -0
- provide/foundation/logger/emoji/matrix.py +209 -0
- provide/foundation/logger/emoji/sets.py +458 -0
- provide/foundation/logger/emoji/types.py +56 -0
- provide/foundation/logger/factories.py +56 -0
- provide/foundation/logger/processors/__init__.py +13 -0
- provide/foundation/logger/processors/main.py +254 -0
- provide/foundation/logger/processors/trace.py +113 -0
- provide/foundation/logger/ratelimit/__init__.py +31 -0
- provide/foundation/logger/ratelimit/limiters.py +294 -0
- provide/foundation/logger/ratelimit/processor.py +203 -0
- provide/foundation/logger/ratelimit/queue_limiter.py +305 -0
- provide/foundation/logger/setup/__init__.py +29 -0
- provide/foundation/logger/setup/coordinator.py +138 -0
- provide/foundation/logger/setup/emoji_resolver.py +64 -0
- provide/foundation/logger/setup/processors.py +85 -0
- provide/foundation/logger/setup/testing.py +39 -0
- provide/foundation/logger/trace.py +38 -0
- provide/foundation/metrics/__init__.py +119 -0
- provide/foundation/metrics/otel.py +122 -0
- provide/foundation/metrics/simple.py +165 -0
- provide/foundation/observability/__init__.py +53 -0
- provide/foundation/observability/openobserve/__init__.py +79 -0
- provide/foundation/observability/openobserve/auth.py +72 -0
- provide/foundation/observability/openobserve/client.py +307 -0
- provide/foundation/observability/openobserve/commands.py +357 -0
- provide/foundation/observability/openobserve/exceptions.py +41 -0
- provide/foundation/observability/openobserve/formatters.py +298 -0
- provide/foundation/observability/openobserve/models.py +134 -0
- provide/foundation/observability/openobserve/otlp.py +320 -0
- provide/foundation/observability/openobserve/search.py +222 -0
- provide/foundation/observability/openobserve/streaming.py +235 -0
- provide/foundation/platform/__init__.py +44 -0
- provide/foundation/platform/detection.py +193 -0
- provide/foundation/platform/info.py +157 -0
- provide/foundation/process/__init__.py +39 -0
- provide/foundation/process/async_runner.py +373 -0
- provide/foundation/process/lifecycle.py +406 -0
- provide/foundation/process/runner.py +390 -0
- provide/foundation/setup/__init__.py +101 -0
- provide/foundation/streams/__init__.py +44 -0
- provide/foundation/streams/console.py +57 -0
- provide/foundation/streams/core.py +65 -0
- provide/foundation/streams/file.py +104 -0
- provide/foundation/testing/__init__.py +166 -0
- provide/foundation/testing/cli.py +227 -0
- provide/foundation/testing/crypto.py +163 -0
- provide/foundation/testing/fixtures.py +49 -0
- provide/foundation/testing/hub.py +23 -0
- provide/foundation/testing/logger.py +106 -0
- provide/foundation/testing/streams.py +54 -0
- provide/foundation/tracer/__init__.py +49 -0
- provide/foundation/tracer/context.py +115 -0
- provide/foundation/tracer/otel.py +135 -0
- provide/foundation/tracer/spans.py +174 -0
- provide/foundation/types.py +32 -0
- provide/foundation/utils/__init__.py +97 -0
- provide/foundation/utils/deps.py +195 -0
- provide/foundation/utils/env.py +491 -0
- provide/foundation/utils/formatting.py +483 -0
- provide/foundation/utils/parsing.py +235 -0
- provide/foundation/utils/rate_limiting.py +112 -0
- provide/foundation/utils/streams.py +67 -0
- provide/foundation/utils/timing.py +93 -0
- provide_foundation-0.0.0.dev0.dist-info/METADATA +469 -0
- provide_foundation-0.0.0.dev0.dist-info/RECORD +149 -0
- provide_foundation-0.0.0.dev0.dist-info/WHEEL +5 -0
- provide_foundation-0.0.0.dev0.dist-info/entry_points.txt +2 -0
- provide_foundation-0.0.0.dev0.dist-info/licenses/LICENSE +201 -0
- provide_foundation-0.0.0.dev0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,112 @@
|
|
1
|
+
"""
|
2
|
+
Rate limiting utilities for Foundation.
|
3
|
+
|
4
|
+
This module provides rate limiting implementations suitable for
|
5
|
+
asynchronous applications, helping to manage request load and prevent abuse.
|
6
|
+
"""
|
7
|
+
|
8
|
+
import asyncio
|
9
|
+
import time
|
10
|
+
from typing import final
|
11
|
+
|
12
|
+
|
13
|
+
@final
|
14
|
+
class TokenBucketRateLimiter:
|
15
|
+
"""
|
16
|
+
A Token Bucket rate limiter for asyncio applications.
|
17
|
+
|
18
|
+
This limiter allows for bursts up to a specified capacity and refills tokens
|
19
|
+
at a constant rate. It is designed to be thread-safe using an asyncio.Lock.
|
20
|
+
"""
|
21
|
+
|
22
|
+
def __init__(self, capacity: float, refill_rate: float) -> None:
|
23
|
+
"""
|
24
|
+
Initialize the TokenBucketRateLimiter.
|
25
|
+
|
26
|
+
Args:
|
27
|
+
capacity: The maximum number of tokens the bucket can hold
|
28
|
+
(burst capacity).
|
29
|
+
refill_rate: The rate at which tokens are refilled per second.
|
30
|
+
"""
|
31
|
+
if capacity <= 0:
|
32
|
+
raise ValueError("Capacity must be positive.")
|
33
|
+
if refill_rate <= 0:
|
34
|
+
raise ValueError("Refill rate must be positive.")
|
35
|
+
|
36
|
+
self._capacity: float = float(capacity)
|
37
|
+
self._refill_rate: float = float(refill_rate)
|
38
|
+
self._tokens: float = float(capacity) # Start with a full bucket
|
39
|
+
self._last_refill_timestamp: float = time.monotonic()
|
40
|
+
self._lock: asyncio.Lock = asyncio.Lock()
|
41
|
+
|
42
|
+
# Cache logger instance to avoid repeated imports
|
43
|
+
self._logger = None
|
44
|
+
try:
|
45
|
+
from provide.foundation.logger import get_logger
|
46
|
+
|
47
|
+
self._logger = get_logger(__name__)
|
48
|
+
self._logger.debug(
|
49
|
+
"🔩🗑️ TokenBucketRateLimiter initialized: "
|
50
|
+
f"capacity={capacity}, refill_rate={refill_rate}"
|
51
|
+
)
|
52
|
+
except ImportError:
|
53
|
+
# Fallback if logger not available
|
54
|
+
pass
|
55
|
+
|
56
|
+
async def _refill_tokens(self) -> None:
|
57
|
+
"""
|
58
|
+
Refills tokens based on the elapsed time since the last refill.
|
59
|
+
This method is not locked internally; caller must hold the lock.
|
60
|
+
"""
|
61
|
+
now = time.monotonic()
|
62
|
+
elapsed_time = now - self._last_refill_timestamp
|
63
|
+
if elapsed_time > 0: # only refill if time has passed
|
64
|
+
tokens_to_add = elapsed_time * self._refill_rate
|
65
|
+
# logger.debug(
|
66
|
+
# f"🔩🗑️ Refilling: elapsed={elapsed_time:.4f}s, "
|
67
|
+
# f"tokens_to_add={tokens_to_add:.4f}, "
|
68
|
+
# f"current_tokens={self._tokens:.4f}"
|
69
|
+
# )
|
70
|
+
self._tokens = min(self._capacity, self._tokens + tokens_to_add)
|
71
|
+
self._last_refill_timestamp = now
|
72
|
+
# logger.debug(
|
73
|
+
# f"🔩🗑️ Refilled: new_tokens={self._tokens:.4f}, "
|
74
|
+
# f"last_refill_timestamp={self._last_refill_timestamp:.4f}"
|
75
|
+
# )
|
76
|
+
|
77
|
+
async def is_allowed(self) -> bool:
|
78
|
+
"""
|
79
|
+
Check if a request is allowed based on available tokens.
|
80
|
+
|
81
|
+
This method is asynchronous and thread-safe. It refills tokens
|
82
|
+
based on elapsed time and then attempts to consume a token.
|
83
|
+
|
84
|
+
Returns:
|
85
|
+
True if the request is allowed, False otherwise.
|
86
|
+
"""
|
87
|
+
async with self._lock:
|
88
|
+
await self._refill_tokens() # Refill before checking
|
89
|
+
|
90
|
+
if self._tokens >= 1.0:
|
91
|
+
self._tokens -= 1.0
|
92
|
+
if self._logger:
|
93
|
+
self._logger.debug(
|
94
|
+
"🔩🗑️✅ Request allowed. Tokens remaining: "
|
95
|
+
f"{self._tokens:.2f}/{self._capacity:.2f}"
|
96
|
+
)
|
97
|
+
return True
|
98
|
+
else:
|
99
|
+
if self._logger:
|
100
|
+
self._logger.warning(
|
101
|
+
"🔩🗑️❌ Request denied. No tokens available. Tokens: "
|
102
|
+
f"{self._tokens:.2f}/{self._capacity:.2f}"
|
103
|
+
)
|
104
|
+
return False
|
105
|
+
|
106
|
+
async def get_current_tokens(self) -> float:
|
107
|
+
"""Returns the current number of tokens, for testing/monitoring."""
|
108
|
+
async with self._lock:
|
109
|
+
# It might be useful to refill before getting, to get the most
|
110
|
+
# up-to-date count
|
111
|
+
# await self._refill_tokens()
|
112
|
+
return self._tokens
|
@@ -0,0 +1,67 @@
|
|
1
|
+
"""Stream utilities for foundation library."""
|
2
|
+
|
3
|
+
import io
|
4
|
+
import sys
|
5
|
+
from typing import TextIO
|
6
|
+
|
7
|
+
|
8
|
+
def get_safe_stderr() -> TextIO:
|
9
|
+
"""Get a safe stderr stream, falling back to StringIO if stderr is not available.
|
10
|
+
|
11
|
+
This is used during initialization when sys.stderr might not be available
|
12
|
+
(e.g., in some embedded Python environments or during testing).
|
13
|
+
|
14
|
+
Returns:
|
15
|
+
A writable text stream, either sys.stderr or io.StringIO()
|
16
|
+
"""
|
17
|
+
return (
|
18
|
+
sys.stderr
|
19
|
+
if hasattr(sys, "stderr") and sys.stderr is not None
|
20
|
+
else io.StringIO()
|
21
|
+
)
|
22
|
+
|
23
|
+
|
24
|
+
def get_foundation_log_stream(output_setting: str) -> TextIO:
|
25
|
+
"""Get the appropriate stream for Foundation internal logging.
|
26
|
+
|
27
|
+
Args:
|
28
|
+
output_setting: One of "stderr", "stdout", or "main"
|
29
|
+
|
30
|
+
Returns:
|
31
|
+
A writable text stream based on the output setting
|
32
|
+
|
33
|
+
Notes:
|
34
|
+
- "stderr": Returns sys.stderr (default, RPC-safe)
|
35
|
+
- "stdout": Returns sys.stdout
|
36
|
+
- "main": Returns the main logger stream from _PROVIDE_LOG_STREAM
|
37
|
+
- Invalid values default to sys.stderr with warning
|
38
|
+
"""
|
39
|
+
if output_setting == "stdout":
|
40
|
+
return sys.stdout
|
41
|
+
elif output_setting == "main":
|
42
|
+
# Import here to avoid circular dependency
|
43
|
+
try:
|
44
|
+
from provide.foundation.streams import get_log_stream
|
45
|
+
|
46
|
+
return get_log_stream()
|
47
|
+
except ImportError:
|
48
|
+
# Fallback if setup module not available during initialization
|
49
|
+
return get_safe_stderr()
|
50
|
+
elif output_setting == "stderr":
|
51
|
+
return get_safe_stderr()
|
52
|
+
else:
|
53
|
+
# Invalid value - warn and default to stderr
|
54
|
+
# Import config logger here to avoid circular dependency
|
55
|
+
try:
|
56
|
+
from provide.foundation.logger.config import _get_config_logger
|
57
|
+
|
58
|
+
_get_config_logger().warning(
|
59
|
+
"[Foundation Config Warning] Invalid FOUNDATION_LOG_OUTPUT value, using stderr",
|
60
|
+
invalid_value=output_setting,
|
61
|
+
valid_options=["stderr", "stdout", "main"],
|
62
|
+
default_used="stderr",
|
63
|
+
)
|
64
|
+
except ImportError:
|
65
|
+
# During early initialization, just use stderr silently
|
66
|
+
pass
|
67
|
+
return get_safe_stderr()
|
@@ -0,0 +1,93 @@
|
|
1
|
+
"""
|
2
|
+
Timing and performance utilities.
|
3
|
+
|
4
|
+
Provides context managers and utilities for timing operations and logging performance.
|
5
|
+
"""
|
6
|
+
|
7
|
+
from collections.abc import Generator
|
8
|
+
from contextlib import contextmanager
|
9
|
+
import contextvars
|
10
|
+
import time
|
11
|
+
from typing import TYPE_CHECKING, Any
|
12
|
+
|
13
|
+
if TYPE_CHECKING:
|
14
|
+
from provide.foundation.logger.base import FoundationLogger
|
15
|
+
|
16
|
+
|
17
|
+
# Context variable for trace_id (applications can set this for request tracing)
|
18
|
+
_PROVIDE_CONTEXT_TRACE_ID = contextvars.ContextVar(
|
19
|
+
"foundation_context_trace_id", default=None
|
20
|
+
)
|
21
|
+
|
22
|
+
|
23
|
+
@contextmanager
|
24
|
+
def timed_block(
|
25
|
+
logger_instance: "FoundationLogger",
|
26
|
+
event_name: str,
|
27
|
+
layer_keys: dict[str, Any] | None = None,
|
28
|
+
initial_kvs: dict[str, Any] | None = None,
|
29
|
+
**extra_kvs: Any,
|
30
|
+
) -> Generator[dict[str, Any], None, None]:
|
31
|
+
"""
|
32
|
+
Context manager that logs the duration of a code block.
|
33
|
+
|
34
|
+
Logs at DEBUG when entering, INFO on success, ERROR on exception.
|
35
|
+
|
36
|
+
Args:
|
37
|
+
logger_instance: Logger to use for output
|
38
|
+
event_name: Name of the operation being timed
|
39
|
+
layer_keys: Semantic layer keys (e.g., llm-specific keys)
|
40
|
+
initial_kvs: Initial key-value pairs to include in logs
|
41
|
+
**extra_kvs: Additional key-value pairs
|
42
|
+
|
43
|
+
Yields:
|
44
|
+
A mutable dict that can be updated with additional context
|
45
|
+
|
46
|
+
Example:
|
47
|
+
>>> with timed_block(logger, "database_query") as ctx:
|
48
|
+
>>> ctx["query"] = "SELECT * FROM users"
|
49
|
+
>>> result = db.query("SELECT * FROM users")
|
50
|
+
>>> ctx["rows"] = len(result)
|
51
|
+
"""
|
52
|
+
# Combine all key-value pairs
|
53
|
+
all_kvs = {}
|
54
|
+
if layer_keys:
|
55
|
+
all_kvs.update(layer_keys)
|
56
|
+
if initial_kvs:
|
57
|
+
all_kvs.update(initial_kvs)
|
58
|
+
all_kvs.update(extra_kvs)
|
59
|
+
|
60
|
+
# Try to get trace_id from context
|
61
|
+
trace_id = _PROVIDE_CONTEXT_TRACE_ID.get()
|
62
|
+
if trace_id and "trace_id" not in all_kvs:
|
63
|
+
all_kvs["trace_id"] = trace_id
|
64
|
+
|
65
|
+
# Create context dict that can be modified
|
66
|
+
context: dict[str, Any] = {}
|
67
|
+
|
68
|
+
# Log start
|
69
|
+
logger_instance.debug(f"{event_name} started", **all_kvs)
|
70
|
+
|
71
|
+
start_time = time.perf_counter()
|
72
|
+
try:
|
73
|
+
yield context
|
74
|
+
|
75
|
+
# Success - calculate duration and log
|
76
|
+
duration = time.perf_counter() - start_time
|
77
|
+
all_kvs.update(context)
|
78
|
+
all_kvs["duration_seconds"] = round(duration, 3)
|
79
|
+
all_kvs["outcome"] = "success"
|
80
|
+
|
81
|
+
logger_instance.info(f"{event_name} completed", **all_kvs)
|
82
|
+
|
83
|
+
except Exception as e:
|
84
|
+
# Error - calculate duration and log with exception
|
85
|
+
duration = time.perf_counter() - start_time
|
86
|
+
all_kvs.update(context)
|
87
|
+
all_kvs["duration_seconds"] = round(duration, 3)
|
88
|
+
all_kvs["outcome"] = "error"
|
89
|
+
all_kvs["error.message"] = str(e)
|
90
|
+
all_kvs["error.type"] = type(e).__name__
|
91
|
+
|
92
|
+
logger_instance.error(f"{event_name} failed", exc_info=True, **all_kvs)
|
93
|
+
raise
|