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,279 @@
1
+ """Registry management for the foundation.
2
+
3
+ Provides both generic multi-dimensional registry functionality and
4
+ specialized command registry management.
5
+ """
6
+
7
+ from collections import defaultdict
8
+ from collections.abc import Iterator
9
+ import threading
10
+ from typing import Any
11
+
12
+ from attrs import define, field
13
+
14
+ from provide.foundation.errors.resources import AlreadyExistsError
15
+ from provide.foundation.logger import get_logger
16
+
17
+ log = get_logger(__name__)
18
+
19
+
20
+ @define(frozen=True, slots=True)
21
+ class RegistryEntry:
22
+ """A single entry in the registry."""
23
+
24
+ name: str
25
+ dimension: str
26
+ value: Any
27
+ metadata: dict[str, Any] = field(factory=lambda: {})
28
+
29
+ @property
30
+ def key(self) -> tuple[str, str]:
31
+ """Get the registry key for this entry."""
32
+ return (self.dimension, self.name)
33
+
34
+
35
+ class Registry:
36
+ """
37
+ Multi-dimensional registry for storing and retrieving objects.
38
+
39
+ Supports hierarchical organization by dimension (component, command, etc.)
40
+ and name within each dimension. This is a generic registry that can be
41
+ used for any type of object storage and retrieval.
42
+
43
+ Thread-safe: All operations are protected by an RLock for safe concurrent access.
44
+ """
45
+
46
+ def __init__(self) -> None:
47
+ """Initialize an empty registry."""
48
+ self._lock = threading.RLock() # Reentrant lock for thread safety
49
+ self._registry: dict[str, dict[str, RegistryEntry]] = defaultdict(dict)
50
+ self._aliases: dict[str, tuple[str, str]] = {}
51
+
52
+ def register(
53
+ self,
54
+ name: str,
55
+ value: Any,
56
+ dimension: str = "default",
57
+ metadata: dict[str, Any] | None = None,
58
+ aliases: list[str] | None = None,
59
+ replace: bool = False,
60
+ ) -> RegistryEntry:
61
+ """
62
+ Register an item in the registry.
63
+
64
+ Args:
65
+ name: Unique name within the dimension
66
+ value: The item to register
67
+ dimension: Registry dimension for categorization
68
+ metadata: Optional metadata about the item
69
+ aliases: Optional list of aliases for this item
70
+ replace: Whether to replace existing entries
71
+
72
+ Returns:
73
+ The created registry entry
74
+
75
+ Raises:
76
+ ValueError: If name already exists and replace=False
77
+ """
78
+ with self._lock:
79
+ if not replace and name in self._registry[dimension]:
80
+ raise AlreadyExistsError(
81
+ f"Item '{name}' already registered in dimension '{dimension}'. "
82
+ "Use replace=True to override.",
83
+ code="REGISTRY_ITEM_EXISTS",
84
+ item_name=name,
85
+ dimension=dimension,
86
+ )
87
+
88
+ entry = RegistryEntry(
89
+ name=name,
90
+ dimension=dimension,
91
+ value=value,
92
+ metadata=metadata or {},
93
+ )
94
+
95
+ self._registry[dimension][name] = entry
96
+
97
+ if aliases:
98
+ for alias in aliases:
99
+ self._aliases[alias] = (dimension, name)
100
+
101
+ log.debug(
102
+ "Registered item",
103
+ name=name,
104
+ dimension=dimension,
105
+ has_metadata=bool(metadata),
106
+ aliases=aliases,
107
+ )
108
+
109
+ return entry
110
+
111
+ def get(
112
+ self,
113
+ name: str,
114
+ dimension: str | None = None,
115
+ ) -> Any | None:
116
+ """
117
+ Get an item from the registry.
118
+
119
+ Args:
120
+ name: Name or alias of the item
121
+ dimension: Optional dimension to search in
122
+
123
+ Returns:
124
+ The registered value or None if not found
125
+ """
126
+ with self._lock:
127
+ if dimension is not None:
128
+ entry = self._registry[dimension].get(name)
129
+ if entry:
130
+ return entry.value
131
+
132
+ if name in self._aliases:
133
+ dim_key, real_name = self._aliases[name]
134
+ if dimension is None or dim_key == dimension:
135
+ entry = self._registry[dim_key].get(real_name)
136
+ if entry:
137
+ return entry.value
138
+
139
+ if dimension is None:
140
+ for dim_registry in self._registry.values():
141
+ if name in dim_registry:
142
+ return dim_registry[name].value
143
+
144
+ return None
145
+
146
+ def get_entry(
147
+ self,
148
+ name: str,
149
+ dimension: str | None = None,
150
+ ) -> RegistryEntry | None:
151
+ """Get the full registry entry."""
152
+ with self._lock:
153
+ if dimension is not None:
154
+ return self._registry[dimension].get(name)
155
+
156
+ if name in self._aliases:
157
+ dim_key, real_name = self._aliases[name]
158
+ if dimension is None or dim_key == dimension:
159
+ return self._registry[dim_key].get(real_name)
160
+
161
+ if dimension is None:
162
+ for dim_registry in self._registry.values():
163
+ if name in dim_registry:
164
+ return dim_registry[name]
165
+
166
+ return None
167
+
168
+ def list_dimension(
169
+ self,
170
+ dimension: str,
171
+ ) -> list[str]:
172
+ """List all names in a dimension."""
173
+ with self._lock:
174
+ return list(self._registry[dimension].keys())
175
+
176
+ def list_all(self) -> dict[str, list[str]]:
177
+ """List all dimensions and their items."""
178
+ with self._lock:
179
+ return {
180
+ dimension: list(items.keys())
181
+ for dimension, items in self._registry.items()
182
+ }
183
+
184
+ def remove(
185
+ self,
186
+ name: str,
187
+ dimension: str | None = None,
188
+ ) -> bool:
189
+ """
190
+ Remove an item from the registry.
191
+
192
+ Returns:
193
+ True if item was removed, False if not found
194
+ """
195
+ with self._lock:
196
+ if dimension is not None:
197
+ if name in self._registry[dimension]:
198
+ del self._registry[dimension][name]
199
+
200
+ aliases_to_remove = [
201
+ alias
202
+ for alias, (dim, n) in self._aliases.items()
203
+ if dim == dimension and n == name
204
+ ]
205
+ for alias in aliases_to_remove:
206
+ del self._aliases[alias]
207
+
208
+ log.debug("Removed item", name=name, dimension=dimension)
209
+ return True
210
+ else:
211
+ for dim_key, dim_registry in self._registry.items():
212
+ if name in dim_registry:
213
+ del dim_registry[name]
214
+
215
+ aliases_to_remove = [
216
+ alias
217
+ for alias, (d, n) in self._aliases.items()
218
+ if d == dim_key and n == name
219
+ ]
220
+ for alias in aliases_to_remove:
221
+ del self._aliases[alias]
222
+
223
+ log.debug("Removed item", name=name, dimension=dim_key)
224
+ return True
225
+
226
+ return False
227
+
228
+ def clear(self, dimension: str | None = None) -> None:
229
+ """Clear the registry or a specific dimension."""
230
+ with self._lock:
231
+ if dimension is not None:
232
+ self._registry[dimension].clear()
233
+
234
+ aliases_to_remove = [
235
+ alias
236
+ for alias, (dim, _) in self._aliases.items()
237
+ if dim == dimension
238
+ ]
239
+ for alias in aliases_to_remove:
240
+ del self._aliases[alias]
241
+ else:
242
+ self._registry.clear()
243
+ self._aliases.clear()
244
+
245
+ def __contains__(self, key: str | tuple[str, str]) -> bool:
246
+ """Check if an item exists in the registry."""
247
+ with self._lock:
248
+ if isinstance(key, tuple):
249
+ dimension, name = key
250
+ return name in self._registry[dimension]
251
+ else:
252
+ return any(key in dim_reg for dim_reg in self._registry.values())
253
+
254
+ def __iter__(self) -> Iterator[RegistryEntry]:
255
+ """Iterate over all registry entries."""
256
+ with self._lock:
257
+ # Create a snapshot to avoid holding lock during iteration
258
+ entries = []
259
+ for dim_registry in self._registry.values():
260
+ entries.extend(dim_registry.values())
261
+ # Yield outside the lock
262
+ yield from entries
263
+
264
+ def __len__(self) -> int:
265
+ """Get total number of registered items."""
266
+ with self._lock:
267
+ return sum(len(dim_reg) for dim_reg in self._registry.values())
268
+
269
+
270
+ # Global registry for commands
271
+ _command_registry = Registry()
272
+
273
+
274
+ def get_command_registry() -> Registry:
275
+ """Get the global command registry."""
276
+ return _command_registry
277
+
278
+
279
+ __all__ = ["Registry", "RegistryEntry", "get_command_registry"]
@@ -0,0 +1,54 @@
1
+ """Type system and Click type mapping utilities."""
2
+
3
+ import types
4
+ import typing
5
+ from typing import Any, get_args, get_origin
6
+
7
+
8
+ def extract_click_type(annotation: Any) -> type:
9
+ """
10
+ Extract a Click-compatible type from a Python type annotation.
11
+
12
+ Handles:
13
+ - Union types (str | None, Union[str, None])
14
+ - Optional types (Optional[str])
15
+ - Regular types (str, int, bool)
16
+
17
+ Args:
18
+ annotation: Type annotation from function signature
19
+
20
+ Returns:
21
+ A type that Click can understand
22
+ """
23
+ # Handle None type
24
+ if annotation is type(None):
25
+ return str
26
+
27
+ # Get the origin and args for generic types
28
+ origin = get_origin(annotation)
29
+ args = get_args(annotation)
30
+
31
+ # Handle Union types (including Optional which is Union[T, None])
32
+ if origin is typing.Union or (
33
+ hasattr(types, "UnionType") and isinstance(annotation, types.UnionType)
34
+ ):
35
+ # For Python 3.10+ union syntax (str | None)
36
+ if hasattr(annotation, "__args__"):
37
+ args = annotation.__args__
38
+
39
+ # Filter out None type to get the actual type
40
+ non_none_types = [t for t in args if t is not type(None)]
41
+
42
+ if non_none_types:
43
+ # Return the first non-None type
44
+ # Could be enhanced to handle Union[str, int] etc.
45
+ return non_none_types[0]
46
+ else:
47
+ # If only None, default to str
48
+ return str
49
+
50
+ # For non-generic types, return as-is
51
+ return annotation
52
+
53
+
54
+ __all__ = ["extract_click_type"]
@@ -0,0 +1,28 @@
1
+ """Type definitions for the hub module."""
2
+
3
+ from typing import Any, Protocol
4
+
5
+ from attrs import define, field
6
+
7
+
8
+ @define(frozen=True, slots=True)
9
+ class RegistryEntry:
10
+ """A single entry in the registry."""
11
+
12
+ name: str
13
+ dimension: str
14
+ value: Any
15
+ metadata: dict[str, Any] = field(factory=lambda: {})
16
+
17
+ @property
18
+ def key(self) -> tuple[str, str]:
19
+ """Get the registry key for this entry."""
20
+ return (self.dimension, self.name)
21
+
22
+
23
+ class Registrable(Protocol):
24
+ """Protocol for objects that can be registered."""
25
+
26
+ __registry_name__: str
27
+ __registry_dimension__: str
28
+ __registry_metadata__: dict[str, Any]
@@ -0,0 +1,41 @@
1
+ #
2
+ # __init__.py
3
+ #
4
+ """
5
+ Foundation Telemetry Logger Sub-package.
6
+ Re-exports key components related to logging functionality.
7
+ """
8
+
9
+ from provide.foundation.logger.base import (
10
+ FoundationLogger, # Class definition
11
+ get_logger, # Factory function
12
+ logger, # Global instance
13
+ setup_logger, # Setup function (consistent naming)
14
+ setup_logging, # Setup function (backward compatibility)
15
+ )
16
+ from provide.foundation.logger.config import (
17
+ LoggingConfig,
18
+ TelemetryConfig,
19
+ )
20
+ from provide.foundation.logger.emoji.matrix import (
21
+ PRIMARY_EMOJI, # Core domain emojis
22
+ SECONDARY_EMOJI, # Core action emojis
23
+ TERTIARY_EMOJI, # Core status emojis
24
+ show_emoji_matrix, # Utility to display emoji configurations
25
+ )
26
+
27
+ __all__ = [
28
+ "PRIMARY_EMOJI",
29
+ "SECONDARY_EMOJI",
30
+ "TERTIARY_EMOJI",
31
+ "FoundationLogger",
32
+ "LoggingConfig",
33
+ "TelemetryConfig",
34
+ "get_logger",
35
+ "logger",
36
+ "setup_logger", # Consistent naming
37
+ "setup_logging", # Backward compatibility
38
+ "show_emoji_matrix",
39
+ ]
40
+
41
+ # 🐍📝
@@ -0,0 +1,22 @@
1
+ #
2
+ # base.py
3
+ #
4
+ """
5
+ Foundation Logger - Main Interface.
6
+
7
+ Re-exports the core logger components.
8
+ """
9
+
10
+ from provide.foundation.logger.core import FoundationLogger, logger
11
+ from provide.foundation.logger.factories import get_logger, setup_logging
12
+
13
+ # Alias for consistent naming convention
14
+ setup_logger = setup_logging
15
+
16
+ __all__ = [
17
+ "FoundationLogger",
18
+ "get_logger",
19
+ "logger",
20
+ "setup_logger", # New consistent naming
21
+ "setup_logging", # Keep for backward compatibility
22
+ ]
@@ -0,0 +1,16 @@
1
+ #
2
+ # __init__.py
3
+ #
4
+ """
5
+ Foundation Logger Configuration Module.
6
+
7
+ Re-exports all configuration classes for convenient importing.
8
+ """
9
+
10
+ from provide.foundation.logger.config.logging import LoggingConfig
11
+ from provide.foundation.logger.config.telemetry import TelemetryConfig
12
+
13
+ __all__ = [
14
+ "LoggingConfig",
15
+ "TelemetryConfig",
16
+ ]
@@ -0,0 +1,40 @@
1
+ #
2
+ # base.py
3
+ #
4
+ """
5
+ Base configuration utilities for Foundation logger.
6
+ """
7
+
8
+ import os
9
+ import sys
10
+
11
+
12
+ def get_config_logger():
13
+ """Get logger for config warnings that respects FOUNDATION_LOG_OUTPUT."""
14
+ import structlog
15
+
16
+ from provide.foundation.utils.streams import get_foundation_log_stream
17
+
18
+ try:
19
+ foundation_output = os.getenv("FOUNDATION_LOG_OUTPUT", "stderr").lower()
20
+ output_stream = get_foundation_log_stream(foundation_output)
21
+ except Exception:
22
+ output_stream = sys.stderr
23
+
24
+ try:
25
+ config = structlog.get_config()
26
+ structlog.configure(
27
+ processors=config.get("processors", [structlog.dev.ConsoleRenderer()]),
28
+ logger_factory=structlog.PrintLoggerFactory(file=output_stream),
29
+ wrapper_class=config.get("wrapper_class", structlog.BoundLogger),
30
+ cache_logger_on_first_use=config.get("cache_logger_on_first_use", True),
31
+ )
32
+ except Exception:
33
+ structlog.configure(
34
+ processors=[structlog.dev.ConsoleRenderer()],
35
+ logger_factory=structlog.PrintLoggerFactory(file=output_stream),
36
+ wrapper_class=structlog.BoundLogger,
37
+ cache_logger_on_first_use=True,
38
+ )
39
+
40
+ return structlog.get_logger("provide.foundation.logger.config")