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,310 @@
|
|
1
|
+
"""Click command and group building functions."""
|
2
|
+
|
3
|
+
import inspect
|
4
|
+
from typing import Any
|
5
|
+
|
6
|
+
import click
|
7
|
+
|
8
|
+
from provide.foundation.hub.info import CommandInfo
|
9
|
+
from provide.foundation.hub.registry import Registry, get_command_registry
|
10
|
+
from provide.foundation.hub.type_mapping import extract_click_type
|
11
|
+
from provide.foundation.logger import get_logger
|
12
|
+
|
13
|
+
log = get_logger(__name__)
|
14
|
+
|
15
|
+
|
16
|
+
def ensure_parent_groups(parent_path: str, registry: Registry) -> None:
|
17
|
+
"""Ensure all parent groups in the path exist, creating them if needed."""
|
18
|
+
parts = parent_path.split(".")
|
19
|
+
|
20
|
+
# Build up the path progressively
|
21
|
+
for i in range(len(parts)):
|
22
|
+
group_path = ".".join(parts[: i + 1])
|
23
|
+
registry_key = group_path
|
24
|
+
|
25
|
+
# Check if this group already exists
|
26
|
+
if not registry.get_entry(registry_key, dimension="command"):
|
27
|
+
# Create a placeholder group
|
28
|
+
def group_func() -> None:
|
29
|
+
"""Auto-generated command group."""
|
30
|
+
pass
|
31
|
+
|
32
|
+
# Set the function name for better debugging
|
33
|
+
group_func.__name__ = f"{parts[i]}_group"
|
34
|
+
|
35
|
+
# Register the group
|
36
|
+
parent = ".".join(parts[:i]) if i > 0 else None
|
37
|
+
|
38
|
+
info = CommandInfo(
|
39
|
+
name=parts[i],
|
40
|
+
func=group_func,
|
41
|
+
description=f"{parts[i].capitalize()} commands",
|
42
|
+
metadata={"is_group": True, "auto_created": True},
|
43
|
+
parent=parent,
|
44
|
+
)
|
45
|
+
|
46
|
+
registry.register(
|
47
|
+
name=registry_key,
|
48
|
+
value=group_func,
|
49
|
+
dimension="command",
|
50
|
+
metadata={
|
51
|
+
"info": info,
|
52
|
+
"description": info.description,
|
53
|
+
"parent": parent,
|
54
|
+
"is_group": True,
|
55
|
+
"auto_created": True,
|
56
|
+
},
|
57
|
+
)
|
58
|
+
|
59
|
+
log.debug(f"Auto-created group: {group_path}")
|
60
|
+
|
61
|
+
|
62
|
+
def build_click_command(
|
63
|
+
name: str,
|
64
|
+
registry: Registry | None = None,
|
65
|
+
) -> click.Command | None:
|
66
|
+
"""
|
67
|
+
Build a Click command from a registered function.
|
68
|
+
|
69
|
+
This function takes a registered command and converts it to a
|
70
|
+
Click command with proper options and arguments based on the
|
71
|
+
function signature.
|
72
|
+
|
73
|
+
Args:
|
74
|
+
name: Command name in registry
|
75
|
+
registry: Custom registry (defaults to global)
|
76
|
+
|
77
|
+
Returns:
|
78
|
+
Click Command or None if not found
|
79
|
+
|
80
|
+
Example:
|
81
|
+
>>> @register_command("greet")
|
82
|
+
>>> def greet(name: str = "World"):
|
83
|
+
>>> print(f"Hello, {name}!")
|
84
|
+
>>>
|
85
|
+
>>> click_cmd = build_click_command("greet")
|
86
|
+
>>> # Now click_cmd can be added to a Click group
|
87
|
+
"""
|
88
|
+
reg = registry or get_command_registry()
|
89
|
+
entry = reg.get_entry(name, dimension="command")
|
90
|
+
|
91
|
+
if not entry:
|
92
|
+
return None
|
93
|
+
|
94
|
+
info = entry.metadata.get("info")
|
95
|
+
if not info:
|
96
|
+
return None
|
97
|
+
|
98
|
+
# If it's already a Click command, return it
|
99
|
+
if info.click_command:
|
100
|
+
return info.click_command
|
101
|
+
|
102
|
+
func = info.func
|
103
|
+
if not callable(func):
|
104
|
+
return None
|
105
|
+
|
106
|
+
# Build Click command from function signature
|
107
|
+
sig = inspect.signature(func)
|
108
|
+
|
109
|
+
# Process parameters - separate arguments and options
|
110
|
+
params = list(sig.parameters.items())
|
111
|
+
arguments = []
|
112
|
+
options = []
|
113
|
+
|
114
|
+
for param_name, param in params:
|
115
|
+
if param_name in ("self", "cls", "ctx"):
|
116
|
+
continue
|
117
|
+
|
118
|
+
has_default = param.default != inspect.Parameter.empty
|
119
|
+
if has_default:
|
120
|
+
options.append((param_name, param))
|
121
|
+
else:
|
122
|
+
arguments.append((param_name, param))
|
123
|
+
|
124
|
+
# Start with the base function
|
125
|
+
decorated_func = func
|
126
|
+
|
127
|
+
# Process options in reverse order (for decorator stacking)
|
128
|
+
for param_name, param in reversed(options):
|
129
|
+
# Create option
|
130
|
+
option_name = f"--{param_name.replace('_', '-')}"
|
131
|
+
if param.annotation != inspect.Parameter.empty:
|
132
|
+
# Extract the actual type from unions/optionals
|
133
|
+
param_type = extract_click_type(param.annotation)
|
134
|
+
|
135
|
+
# Use type annotation
|
136
|
+
if param_type == bool:
|
137
|
+
decorated_func = click.option(
|
138
|
+
option_name,
|
139
|
+
is_flag=True,
|
140
|
+
default=param.default,
|
141
|
+
help=f"{param_name} flag",
|
142
|
+
)(decorated_func)
|
143
|
+
else:
|
144
|
+
decorated_func = click.option(
|
145
|
+
option_name,
|
146
|
+
type=param_type,
|
147
|
+
default=param.default,
|
148
|
+
help=f"{param_name} option",
|
149
|
+
)(decorated_func)
|
150
|
+
else:
|
151
|
+
decorated_func = click.option(
|
152
|
+
option_name,
|
153
|
+
default=param.default,
|
154
|
+
help=f"{param_name} option",
|
155
|
+
)(decorated_func)
|
156
|
+
|
157
|
+
# Process arguments in reverse order
|
158
|
+
# When we apply decorators programmatically, the last one applied
|
159
|
+
# becomes the outermost decorator, which Click sees first
|
160
|
+
for param_name, param in reversed(arguments):
|
161
|
+
# Create argument
|
162
|
+
if param.annotation != inspect.Parameter.empty:
|
163
|
+
# Extract the actual type from unions/optionals
|
164
|
+
param_type = extract_click_type(param.annotation)
|
165
|
+
decorated_func = click.argument(
|
166
|
+
param_name,
|
167
|
+
type=param_type,
|
168
|
+
)(decorated_func)
|
169
|
+
else:
|
170
|
+
decorated_func = click.argument(param_name)(decorated_func)
|
171
|
+
|
172
|
+
# Create the Click command with the decorated function
|
173
|
+
cmd = click.Command(
|
174
|
+
name=info.name,
|
175
|
+
callback=decorated_func,
|
176
|
+
help=info.description,
|
177
|
+
hidden=info.hidden,
|
178
|
+
)
|
179
|
+
|
180
|
+
# Copy over the params from the decorated function (Click stores them there)
|
181
|
+
# Note: Click params are in reverse order of decoration, but for the Command
|
182
|
+
# we need them in the correct positional order
|
183
|
+
if hasattr(decorated_func, "__click_params__"):
|
184
|
+
cmd.params = list(reversed(decorated_func.__click_params__))
|
185
|
+
|
186
|
+
return cmd
|
187
|
+
|
188
|
+
|
189
|
+
def create_command_group(
|
190
|
+
name: str = "cli",
|
191
|
+
commands: list[str] | None = None,
|
192
|
+
registry: Registry | None = None,
|
193
|
+
**kwargs: Any,
|
194
|
+
) -> click.Group:
|
195
|
+
"""
|
196
|
+
Create a Click group with registered commands.
|
197
|
+
|
198
|
+
Args:
|
199
|
+
name: Name for the CLI group
|
200
|
+
commands: List of command names to include (None = all)
|
201
|
+
registry: Custom registry (defaults to global)
|
202
|
+
**kwargs: Additional Click Group options
|
203
|
+
|
204
|
+
Returns:
|
205
|
+
Click Group with registered commands
|
206
|
+
|
207
|
+
Example:
|
208
|
+
>>> # Register some commands
|
209
|
+
>>> @register_command("init")
|
210
|
+
>>> def init_cmd():
|
211
|
+
>>> pass
|
212
|
+
>>>
|
213
|
+
>>> # Create CLI group
|
214
|
+
>>> cli = create_command_group("myapp")
|
215
|
+
>>>
|
216
|
+
>>> # Run the CLI
|
217
|
+
>>> if __name__ == "__main__":
|
218
|
+
>>> cli()
|
219
|
+
"""
|
220
|
+
reg = registry or get_command_registry()
|
221
|
+
group = click.Group(name=name, **kwargs)
|
222
|
+
|
223
|
+
# Build nested command structure
|
224
|
+
groups: dict[str, click.Group] = {}
|
225
|
+
|
226
|
+
# Get commands to include
|
227
|
+
if commands is None:
|
228
|
+
commands = reg.list_dimension("command")
|
229
|
+
|
230
|
+
# Sort commands to ensure parents are created before children
|
231
|
+
sorted_commands = sorted(commands, key=lambda x: x.count("."))
|
232
|
+
|
233
|
+
# First pass: create all groups
|
234
|
+
for cmd_name in sorted_commands:
|
235
|
+
entry = reg.get_entry(cmd_name, dimension="command")
|
236
|
+
if not entry:
|
237
|
+
continue
|
238
|
+
|
239
|
+
info = entry.metadata.get("info")
|
240
|
+
if not info:
|
241
|
+
continue
|
242
|
+
|
243
|
+
# Check if this is a group
|
244
|
+
if entry.metadata.get("is_group"):
|
245
|
+
parent = entry.metadata.get("parent")
|
246
|
+
# Extract the actual group name (without parent prefix)
|
247
|
+
actual_name = cmd_name.split(".")[-1] if parent else cmd_name
|
248
|
+
|
249
|
+
subgroup = click.Group(
|
250
|
+
name=actual_name,
|
251
|
+
help=info.description,
|
252
|
+
hidden=info.hidden,
|
253
|
+
)
|
254
|
+
groups[cmd_name] = subgroup
|
255
|
+
|
256
|
+
# Add to parent or root
|
257
|
+
if parent:
|
258
|
+
# Handle multi-level parents with dot notation
|
259
|
+
parent_key = parent
|
260
|
+
if parent_key in groups:
|
261
|
+
groups[parent_key].add_command(subgroup)
|
262
|
+
else:
|
263
|
+
# Parent should have been created, add to root as fallback
|
264
|
+
group.add_command(subgroup)
|
265
|
+
else:
|
266
|
+
group.add_command(subgroup)
|
267
|
+
|
268
|
+
# Second pass: add commands to groups
|
269
|
+
for cmd_name in sorted_commands:
|
270
|
+
entry = reg.get_entry(cmd_name, dimension="command")
|
271
|
+
if not entry:
|
272
|
+
continue
|
273
|
+
|
274
|
+
info = entry.metadata.get("info")
|
275
|
+
if not info or info.hidden or entry.metadata.get("is_group"):
|
276
|
+
continue
|
277
|
+
|
278
|
+
# Build Click command
|
279
|
+
click_cmd = build_click_command(cmd_name, registry=reg)
|
280
|
+
if click_cmd:
|
281
|
+
parent = entry.metadata.get("parent")
|
282
|
+
|
283
|
+
# Update command name if it has a parent
|
284
|
+
if parent:
|
285
|
+
# Extract the actual command name (without parent prefix)
|
286
|
+
parts = cmd_name.split(".")
|
287
|
+
parent_parts = parent.split(".")
|
288
|
+
# Remove parent parts from command name
|
289
|
+
cmd_parts = parts[len(parent_parts) :]
|
290
|
+
click_cmd.name = cmd_parts[0] if cmd_parts else parts[-1]
|
291
|
+
|
292
|
+
# Add to parent group or root
|
293
|
+
if parent:
|
294
|
+
parent_key = parent
|
295
|
+
if parent_key in groups:
|
296
|
+
groups[parent_key].add_command(click_cmd)
|
297
|
+
else:
|
298
|
+
# Parent not found, add to root
|
299
|
+
group.add_command(click_cmd)
|
300
|
+
else:
|
301
|
+
group.add_command(click_cmd)
|
302
|
+
|
303
|
+
return group
|
304
|
+
|
305
|
+
|
306
|
+
__all__ = [
|
307
|
+
"build_click_command",
|
308
|
+
"create_command_group",
|
309
|
+
"ensure_parent_groups",
|
310
|
+
]
|
@@ -0,0 +1,42 @@
|
|
1
|
+
"""
|
2
|
+
Command registration and management for the hub.
|
3
|
+
|
4
|
+
This module now re-exports from the split modules for backward compatibility.
|
5
|
+
"""
|
6
|
+
|
7
|
+
# Core hub features (always available)
|
8
|
+
from provide.foundation.hub.decorators import register_command
|
9
|
+
from provide.foundation.hub.info import CommandInfo
|
10
|
+
from provide.foundation.hub.registry import get_command_registry
|
11
|
+
|
12
|
+
|
13
|
+
# CLI features (require click) - lazy loaded
|
14
|
+
def __getattr__(name: str):
|
15
|
+
"""Support lazy loading of CLI-dependent features."""
|
16
|
+
if name in ("build_click_command", "create_command_group"):
|
17
|
+
try:
|
18
|
+
from provide.foundation.hub.click_builder import (
|
19
|
+
build_click_command,
|
20
|
+
create_command_group,
|
21
|
+
)
|
22
|
+
|
23
|
+
if name == "build_click_command":
|
24
|
+
return build_click_command
|
25
|
+
elif name == "create_command_group":
|
26
|
+
return create_command_group
|
27
|
+
except ImportError as e:
|
28
|
+
if "click" in str(e):
|
29
|
+
raise ImportError(
|
30
|
+
f"CLI feature '{name}' requires: pip install 'provide-foundation[cli]'"
|
31
|
+
) from e
|
32
|
+
raise
|
33
|
+
raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
|
34
|
+
|
35
|
+
|
36
|
+
__all__ = [
|
37
|
+
"CommandInfo",
|
38
|
+
"build_click_command",
|
39
|
+
"create_command_group",
|
40
|
+
"get_command_registry",
|
41
|
+
"register_command",
|
42
|
+
]
|