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.
Files changed (149) hide show
  1. provide/__init__.py +15 -0
  2. provide/foundation/__init__.py +155 -0
  3. provide/foundation/_version.py +58 -0
  4. provide/foundation/cli/__init__.py +67 -0
  5. provide/foundation/cli/commands/__init__.py +3 -0
  6. provide/foundation/cli/commands/deps.py +71 -0
  7. provide/foundation/cli/commands/logs/__init__.py +63 -0
  8. provide/foundation/cli/commands/logs/generate.py +357 -0
  9. provide/foundation/cli/commands/logs/generate_old.py +569 -0
  10. provide/foundation/cli/commands/logs/query.py +174 -0
  11. provide/foundation/cli/commands/logs/send.py +166 -0
  12. provide/foundation/cli/commands/logs/tail.py +112 -0
  13. provide/foundation/cli/decorators.py +262 -0
  14. provide/foundation/cli/main.py +65 -0
  15. provide/foundation/cli/testing.py +220 -0
  16. provide/foundation/cli/utils.py +210 -0
  17. provide/foundation/config/__init__.py +106 -0
  18. provide/foundation/config/base.py +295 -0
  19. provide/foundation/config/env.py +369 -0
  20. provide/foundation/config/loader.py +311 -0
  21. provide/foundation/config/manager.py +387 -0
  22. provide/foundation/config/schema.py +284 -0
  23. provide/foundation/config/sync.py +281 -0
  24. provide/foundation/config/types.py +78 -0
  25. provide/foundation/config/validators.py +80 -0
  26. provide/foundation/console/__init__.py +29 -0
  27. provide/foundation/console/input.py +364 -0
  28. provide/foundation/console/output.py +178 -0
  29. provide/foundation/context/__init__.py +12 -0
  30. provide/foundation/context/core.py +356 -0
  31. provide/foundation/core.py +20 -0
  32. provide/foundation/crypto/__init__.py +182 -0
  33. provide/foundation/crypto/algorithms.py +111 -0
  34. provide/foundation/crypto/certificates.py +896 -0
  35. provide/foundation/crypto/checksums.py +301 -0
  36. provide/foundation/crypto/constants.py +57 -0
  37. provide/foundation/crypto/hashing.py +265 -0
  38. provide/foundation/crypto/keys.py +188 -0
  39. provide/foundation/crypto/signatures.py +144 -0
  40. provide/foundation/crypto/utils.py +164 -0
  41. provide/foundation/errors/__init__.py +96 -0
  42. provide/foundation/errors/auth.py +73 -0
  43. provide/foundation/errors/base.py +81 -0
  44. provide/foundation/errors/config.py +103 -0
  45. provide/foundation/errors/context.py +299 -0
  46. provide/foundation/errors/decorators.py +484 -0
  47. provide/foundation/errors/handlers.py +360 -0
  48. provide/foundation/errors/integration.py +105 -0
  49. provide/foundation/errors/platform.py +37 -0
  50. provide/foundation/errors/process.py +140 -0
  51. provide/foundation/errors/resources.py +133 -0
  52. provide/foundation/errors/runtime.py +160 -0
  53. provide/foundation/errors/safe_decorators.py +133 -0
  54. provide/foundation/errors/types.py +276 -0
  55. provide/foundation/file/__init__.py +79 -0
  56. provide/foundation/file/atomic.py +157 -0
  57. provide/foundation/file/directory.py +134 -0
  58. provide/foundation/file/formats.py +236 -0
  59. provide/foundation/file/lock.py +175 -0
  60. provide/foundation/file/safe.py +179 -0
  61. provide/foundation/file/utils.py +170 -0
  62. provide/foundation/hub/__init__.py +88 -0
  63. provide/foundation/hub/click_builder.py +310 -0
  64. provide/foundation/hub/commands.py +42 -0
  65. provide/foundation/hub/components.py +640 -0
  66. provide/foundation/hub/decorators.py +244 -0
  67. provide/foundation/hub/info.py +32 -0
  68. provide/foundation/hub/manager.py +446 -0
  69. provide/foundation/hub/registry.py +279 -0
  70. provide/foundation/hub/type_mapping.py +54 -0
  71. provide/foundation/hub/types.py +28 -0
  72. provide/foundation/logger/__init__.py +41 -0
  73. provide/foundation/logger/base.py +22 -0
  74. provide/foundation/logger/config/__init__.py +16 -0
  75. provide/foundation/logger/config/base.py +40 -0
  76. provide/foundation/logger/config/logging.py +394 -0
  77. provide/foundation/logger/config/telemetry.py +188 -0
  78. provide/foundation/logger/core.py +239 -0
  79. provide/foundation/logger/custom_processors.py +172 -0
  80. provide/foundation/logger/emoji/__init__.py +44 -0
  81. provide/foundation/logger/emoji/matrix.py +209 -0
  82. provide/foundation/logger/emoji/sets.py +458 -0
  83. provide/foundation/logger/emoji/types.py +56 -0
  84. provide/foundation/logger/factories.py +56 -0
  85. provide/foundation/logger/processors/__init__.py +13 -0
  86. provide/foundation/logger/processors/main.py +254 -0
  87. provide/foundation/logger/processors/trace.py +113 -0
  88. provide/foundation/logger/ratelimit/__init__.py +31 -0
  89. provide/foundation/logger/ratelimit/limiters.py +294 -0
  90. provide/foundation/logger/ratelimit/processor.py +203 -0
  91. provide/foundation/logger/ratelimit/queue_limiter.py +305 -0
  92. provide/foundation/logger/setup/__init__.py +29 -0
  93. provide/foundation/logger/setup/coordinator.py +138 -0
  94. provide/foundation/logger/setup/emoji_resolver.py +64 -0
  95. provide/foundation/logger/setup/processors.py +85 -0
  96. provide/foundation/logger/setup/testing.py +39 -0
  97. provide/foundation/logger/trace.py +38 -0
  98. provide/foundation/metrics/__init__.py +119 -0
  99. provide/foundation/metrics/otel.py +122 -0
  100. provide/foundation/metrics/simple.py +165 -0
  101. provide/foundation/observability/__init__.py +53 -0
  102. provide/foundation/observability/openobserve/__init__.py +79 -0
  103. provide/foundation/observability/openobserve/auth.py +72 -0
  104. provide/foundation/observability/openobserve/client.py +307 -0
  105. provide/foundation/observability/openobserve/commands.py +357 -0
  106. provide/foundation/observability/openobserve/exceptions.py +41 -0
  107. provide/foundation/observability/openobserve/formatters.py +298 -0
  108. provide/foundation/observability/openobserve/models.py +134 -0
  109. provide/foundation/observability/openobserve/otlp.py +320 -0
  110. provide/foundation/observability/openobserve/search.py +222 -0
  111. provide/foundation/observability/openobserve/streaming.py +235 -0
  112. provide/foundation/platform/__init__.py +44 -0
  113. provide/foundation/platform/detection.py +193 -0
  114. provide/foundation/platform/info.py +157 -0
  115. provide/foundation/process/__init__.py +39 -0
  116. provide/foundation/process/async_runner.py +373 -0
  117. provide/foundation/process/lifecycle.py +406 -0
  118. provide/foundation/process/runner.py +390 -0
  119. provide/foundation/setup/__init__.py +101 -0
  120. provide/foundation/streams/__init__.py +44 -0
  121. provide/foundation/streams/console.py +57 -0
  122. provide/foundation/streams/core.py +65 -0
  123. provide/foundation/streams/file.py +104 -0
  124. provide/foundation/testing/__init__.py +166 -0
  125. provide/foundation/testing/cli.py +227 -0
  126. provide/foundation/testing/crypto.py +163 -0
  127. provide/foundation/testing/fixtures.py +49 -0
  128. provide/foundation/testing/hub.py +23 -0
  129. provide/foundation/testing/logger.py +106 -0
  130. provide/foundation/testing/streams.py +54 -0
  131. provide/foundation/tracer/__init__.py +49 -0
  132. provide/foundation/tracer/context.py +115 -0
  133. provide/foundation/tracer/otel.py +135 -0
  134. provide/foundation/tracer/spans.py +174 -0
  135. provide/foundation/types.py +32 -0
  136. provide/foundation/utils/__init__.py +97 -0
  137. provide/foundation/utils/deps.py +195 -0
  138. provide/foundation/utils/env.py +491 -0
  139. provide/foundation/utils/formatting.py +483 -0
  140. provide/foundation/utils/parsing.py +235 -0
  141. provide/foundation/utils/rate_limiting.py +112 -0
  142. provide/foundation/utils/streams.py +67 -0
  143. provide/foundation/utils/timing.py +93 -0
  144. provide_foundation-0.0.0.dev0.dist-info/METADATA +469 -0
  145. provide_foundation-0.0.0.dev0.dist-info/RECORD +149 -0
  146. provide_foundation-0.0.0.dev0.dist-info/WHEEL +5 -0
  147. provide_foundation-0.0.0.dev0.dist-info/entry_points.txt +2 -0
  148. provide_foundation-0.0.0.dev0.dist-info/licenses/LICENSE +201 -0
  149. 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