hieraconf


Namehieraconf JSON
Version 0.1.0 PyPI version JSON
download
home_pageNone
SummaryGeneric lazy dataclass configuration framework with dual-axis inheritance and contextvars-based resolution
upload_time2025-11-01 23:14:18
maintainerNone
docs_urlNone
authorNone
requires_python>=3.10
licenseMIT
keywords configuration contextvars dataclass hierarchical inheritance lazy
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # lazy-config

**Generic lazy dataclass configuration framework with dual-axis inheritance**

[![PyPI version](https://badge.fury.io/py/lazy-config.svg)](https://badge.fury.io/py/lazy-config)
[![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

## Features

- **Lazy Dataclass Factory**: Dynamically create dataclasses with lazy field resolution
- **Dual-Axis Inheritance**: 
  - X-Axis: Context hierarchy (Step → Pipeline → Global)
  - Y-Axis: Sibling inheritance within same context
- **Contextvars-Based**: Uses Python's `contextvars` for clean context management
- **UI Integration**: Placeholder text generation for configuration forms
- **Thread-Safe**: Thread-local global configuration storage
- **100% Generic**: No application-specific dependencies
- **Pure Stdlib**: No external dependencies

## Quick Start

### Simple Usage (Manual Factory)

```python
from dataclasses import dataclass
from lazy_config import LazyDataclassFactory, config_context, set_base_config_type

# Define your base configuration
@dataclass
class MyConfig:
    output_dir: str = "/tmp"
    num_workers: int = 4
    debug: bool = False

# Initialize the framework with your base config type
set_base_config_type(MyConfig)

# Create lazy version manually
LazyMyConfig = LazyDataclassFactory.make_lazy_simple(MyConfig)

# Use with context
concrete_config = MyConfig(output_dir="/data", num_workers=8)

with config_context(concrete_config):
    lazy_cfg = LazyMyConfig()
    print(lazy_cfg.output_dir)  # "/data" (resolved from context)
    print(lazy_cfg.num_workers)  # 8 (resolved from context)
    print(lazy_cfg.debug)        # False (inherited from defaults)
```

### Setting Up Global Config Context (For Advanced Usage)

When using the decorator pattern with `auto_create_decorator`, you need to establish the global configuration context for lazy resolution:

```python
from lazy_config import ensure_global_config_context

# After creating your global config instance
global_config = GlobalPipelineConfig(
    num_workers=8,
    # ... other fields
)

# REQUIRED: Establish global config context for lazy resolution
ensure_global_config_context(GlobalPipelineConfig, global_config)

# Now lazy configs can resolve from the global context
```

**Key differences:**
- `set_base_config_type(MyConfig)`: Sets the **type** (class) for the framework
- `ensure_global_config_context(GlobalConfig, instance)`: Sets the **instance** (concrete values) for resolution
- Call `ensure_global_config_context()` at application startup (GUI) or before pipeline execution

## Installation

```bash
pip install lazy-config
```

## Automatic Lazy Config Generation with Decorators

For more complex applications with multiple config types, use the `auto_create_decorator` pattern to automatically generate lazy versions and field injection decorators:

```python
from dataclasses import dataclass
from lazy_config import auto_create_decorator, config_context

# Step 1: Create a global config class with "Global" prefix and apply auto_create_decorator
@auto_create_decorator
@dataclass
class GlobalPipelineConfig:
    base_output_dir: str = "/tmp"
    verbose: bool = False

# This automatically creates:
# - A decorator named `global_pipeline_config` (snake_case of class name)
#   that you can use to decorate other config classes
# - A lazy class `PipelineConfig` (removes "Global" prefix) for lazy resolution

# Step 2: Use the generated decorator on other config classes
@global_pipeline_config  # Automatically creates LazyStepConfig
@dataclass
class StepConfig:
    step_name: str = "default_step"
    iterations: int = 100

@global_pipeline_config  # Automatically creates LazyDatabaseConfig  
@dataclass
class DatabaseConfig:
    host: str = "localhost"
    port: int = 5432

# The decorator automatically:
# - Creates lazy versions: LazyStepConfig, LazyDatabaseConfig
# - Registers them for potential field injection into GlobalPipelineConfig
# - Makes them available in your module namespace
```

**Key Benefits:**
- **Auto-generated lazy classes**: Each decorated config automatically gets a lazy version
- **Simplified imports**: Lazy classes are automatically added to your module
- **Decorator factory**: `auto_create_decorator` generates a decorator specific to your global config
- **Type-safe**: All generated classes are proper dataclasses with full IDE support

### Field Injection Behavior

When you use the generated decorator (e.g., `@global_pipeline_config`), the decorated class is automatically **injected as a field** into the global config class:

```python
from dataclasses import dataclass
from lazy_config import auto_create_decorator

# Create global config with auto_create_decorator
@auto_create_decorator
@dataclass
class GlobalPipelineConfig:
    num_workers: int = 1

# This creates:
# - A decorator named `global_pipeline_config`
# - A lazy class named `PipelineConfig`

# Use the decorator on a new config class
@global_pipeline_config
@dataclass
class WellFilterConfig:
    well_filter: str = None
    mode: str = "include"

# After module loading, GlobalPipelineConfig automatically has:
# - well_filter_config: WellFilterConfig = WellFilterConfig()
# And LazyWellFilterConfig is auto-created
```

**How it works:**
- Decorated classes are injected as fields into `GlobalPipelineConfig`
- Field name is snake_case of class name (e.g., `WellFilterConfig` → `well_filter_config`)
- Lazy version is automatically created (e.g., `LazyWellFilterConfig`)
- Injection happens at end of module loading via `_inject_all_pending_fields()`

This enables a clean, modular configuration structure where each component's config is automatically part of the global configuration.

### Decorator Parameters

The generated decorator (e.g., `@global_pipeline_config`) supports optional parameters:

#### `inherit_as_none` (Default: `True`)

Sets all inherited fields from parent classes to `None` by default, enabling proper lazy resolution:

```python
@dataclass
class BaseConfig:
    timeout: int = 30
    retries: int = 3

@global_pipeline_config(inherit_as_none=True)  # Default behavior
@dataclass
class ServiceConfig(BaseConfig):
    service_name: str = "my-service"
    # timeout and retries automatically set to None for lazy inheritance

# This allows ServiceConfig to inherit timeout/retries from context
# rather than using the base class defaults
```

**Why this matters:**
- Enables polymorphic access without type-specific attribute names
- Critical for dual-axis inheritance with multiple inheritance
- Uses `InheritAsNoneMeta` metaclass internally

#### `ui_hidden` (Default: `False`)

Hides configs from UI while still applying decorator behavior and keeping them in the resolution context:

```python
@global_pipeline_config(ui_hidden=True)
@dataclass
class InternalConfig:
    internal_setting: str = "hidden"
    # This config won't appear in UI but is still available for inheritance
```

**Use cases:**
- Intermediate configs that are only inherited by other configs
- Internal implementation details not meant for user configuration
- Base classes that should never be directly instantiated in UI

### Nested Dataclass Lazification

When creating a lazy dataclass, **nested dataclass fields are automatically converted** to their lazy versions:

```python
from dataclasses import dataclass
from lazy_config import LazyDataclassFactory

@dataclass
class DatabaseConfig:
    host: str = "localhost"
    port: int = 5432

@dataclass
class AppConfig:
    db_config: DatabaseConfig = DatabaseConfig()
    app_name: str = "MyApp"

# Create lazy version - nested configs are automatically lazified
LazyAppConfig = LazyDataclassFactory.make_lazy_simple(AppConfig)

# The db_config field is automatically converted to LazyDatabaseConfig
# You don't need to manually create LazyDatabaseConfig first!
```

**Benefits:**
- No need to manually create lazy versions of nested configs
- Preserves field metadata (e.g., `ui_hidden` flag)
- Creates default factories for Optional dataclass fields
- Uses `register_lazy_type_mapping()` internally

## Why lazy-config?

**Before** (Manual parameter passing):
```python
def process_step(data, output_dir, num_workers, debug, ...):
    # Pass 20+ parameters through every function
    result = sub_process(data, output_dir, num_workers, debug, ...)
    return result

def sub_process(data, output_dir, num_workers, debug, ...):
    # Repeat parameter declarations everywhere
    ...
```

**After** (lazy-config):
```python
@dataclass
class StepConfig:
    output_dir: str = None
    num_workers: int = None
    debug: bool = None

def process_step(data, config: LazyStepConfig):
    # Config fields resolve automatically from context
    print(config.output_dir)  # Resolved from context hierarchy
    result = sub_process(data, config)
    return result
```

## Advanced Features

### Dual-Axis Inheritance

```python
# X-Axis: Context hierarchy
with config_context(global_config):
    with config_context(pipeline_config):
        with config_context(step_config):
            # Resolves: step → pipeline → global → defaults
            value = lazy_config.some_field

# Y-Axis: Sibling inheritance (MRO-based)
@dataclass
class BaseConfig:
    field_a: str = "base"

@dataclass
class SpecializedConfig(BaseConfig):
    field_b: str = "specialized"

# SpecializedConfig inherits field_a from BaseConfig
```

### Placeholder Generation for UI

```python
from lazy_config import LazyDefaultPlaceholderService

service = LazyDefaultPlaceholderService()

# Generate placeholder text showing inherited values
placeholder = service.get_placeholder_text(
    lazy_config,
    "output_dir",
    available_configs
)
# Returns: "Inherited: /data (from GlobalConfig)"
```

### Cache Warming

```python
from lazy_config import prewarm_config_analysis_cache

# Pre-warm caches for faster runtime resolution
prewarm_config_analysis_cache([GlobalConfig, PipelineConfig, StepConfig])
```

## Architecture

### Dual-Axis Resolution

The framework uses pure MRO-based dual-axis resolution:

**X-Axis (Context Hierarchy)**:
```
Step Context → Pipeline Context → Global Context → Static Defaults
```

**Y-Axis (MRO Traversal)**:
```
Most specific class → Least specific class (following Python's MRO)
```

**How it works:**
1. Context hierarchy is flattened into a single `available_configs` dict
2. For each field resolution, traverse the requesting object's MRO from most to least specific
3. For each MRO class, check if there's a config instance in `available_configs` with a concrete (non-None) value
4. Return the first concrete value found

## Documentation

Full documentation available at [lazy-config.readthedocs.io](https://lazy-config.readthedocs.io)

## Requirements

- Python 3.10+
- No external dependencies (pure stdlib)

## License

MIT License - see LICENSE file for details

## Contributing

Contributions welcome! Please see CONTRIBUTING.md for guidelines.

## Credits

Developed by Tristan Simas as part of the OpenHCS project.

            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "hieraconf",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.10",
    "maintainer_email": null,
    "keywords": "configuration, contextvars, dataclass, hierarchical, inheritance, lazy",
    "author": null,
    "author_email": "Tristan Simas <tristan.simas@mail.mcgill.ca>",
    "download_url": "https://files.pythonhosted.org/packages/1f/25/c84e888ad7019ec075d0d54f62d8f685132edc03cff70a41d04fe320694b/hieraconf-0.1.0.tar.gz",
    "platform": null,
    "description": "# lazy-config\n\n**Generic lazy dataclass configuration framework with dual-axis inheritance**\n\n[![PyPI version](https://badge.fury.io/py/lazy-config.svg)](https://badge.fury.io/py/lazy-config)\n[![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n\n## Features\n\n- **Lazy Dataclass Factory**: Dynamically create dataclasses with lazy field resolution\n- **Dual-Axis Inheritance**: \n  - X-Axis: Context hierarchy (Step \u2192 Pipeline \u2192 Global)\n  - Y-Axis: Sibling inheritance within same context\n- **Contextvars-Based**: Uses Python's `contextvars` for clean context management\n- **UI Integration**: Placeholder text generation for configuration forms\n- **Thread-Safe**: Thread-local global configuration storage\n- **100% Generic**: No application-specific dependencies\n- **Pure Stdlib**: No external dependencies\n\n## Quick Start\n\n### Simple Usage (Manual Factory)\n\n```python\nfrom dataclasses import dataclass\nfrom lazy_config import LazyDataclassFactory, config_context, set_base_config_type\n\n# Define your base configuration\n@dataclass\nclass MyConfig:\n    output_dir: str = \"/tmp\"\n    num_workers: int = 4\n    debug: bool = False\n\n# Initialize the framework with your base config type\nset_base_config_type(MyConfig)\n\n# Create lazy version manually\nLazyMyConfig = LazyDataclassFactory.make_lazy_simple(MyConfig)\n\n# Use with context\nconcrete_config = MyConfig(output_dir=\"/data\", num_workers=8)\n\nwith config_context(concrete_config):\n    lazy_cfg = LazyMyConfig()\n    print(lazy_cfg.output_dir)  # \"/data\" (resolved from context)\n    print(lazy_cfg.num_workers)  # 8 (resolved from context)\n    print(lazy_cfg.debug)        # False (inherited from defaults)\n```\n\n### Setting Up Global Config Context (For Advanced Usage)\n\nWhen using the decorator pattern with `auto_create_decorator`, you need to establish the global configuration context for lazy resolution:\n\n```python\nfrom lazy_config import ensure_global_config_context\n\n# After creating your global config instance\nglobal_config = GlobalPipelineConfig(\n    num_workers=8,\n    # ... other fields\n)\n\n# REQUIRED: Establish global config context for lazy resolution\nensure_global_config_context(GlobalPipelineConfig, global_config)\n\n# Now lazy configs can resolve from the global context\n```\n\n**Key differences:**\n- `set_base_config_type(MyConfig)`: Sets the **type** (class) for the framework\n- `ensure_global_config_context(GlobalConfig, instance)`: Sets the **instance** (concrete values) for resolution\n- Call `ensure_global_config_context()` at application startup (GUI) or before pipeline execution\n\n## Installation\n\n```bash\npip install lazy-config\n```\n\n## Automatic Lazy Config Generation with Decorators\n\nFor more complex applications with multiple config types, use the `auto_create_decorator` pattern to automatically generate lazy versions and field injection decorators:\n\n```python\nfrom dataclasses import dataclass\nfrom lazy_config import auto_create_decorator, config_context\n\n# Step 1: Create a global config class with \"Global\" prefix and apply auto_create_decorator\n@auto_create_decorator\n@dataclass\nclass GlobalPipelineConfig:\n    base_output_dir: str = \"/tmp\"\n    verbose: bool = False\n\n# This automatically creates:\n# - A decorator named `global_pipeline_config` (snake_case of class name)\n#   that you can use to decorate other config classes\n# - A lazy class `PipelineConfig` (removes \"Global\" prefix) for lazy resolution\n\n# Step 2: Use the generated decorator on other config classes\n@global_pipeline_config  # Automatically creates LazyStepConfig\n@dataclass\nclass StepConfig:\n    step_name: str = \"default_step\"\n    iterations: int = 100\n\n@global_pipeline_config  # Automatically creates LazyDatabaseConfig  \n@dataclass\nclass DatabaseConfig:\n    host: str = \"localhost\"\n    port: int = 5432\n\n# The decorator automatically:\n# - Creates lazy versions: LazyStepConfig, LazyDatabaseConfig\n# - Registers them for potential field injection into GlobalPipelineConfig\n# - Makes them available in your module namespace\n```\n\n**Key Benefits:**\n- **Auto-generated lazy classes**: Each decorated config automatically gets a lazy version\n- **Simplified imports**: Lazy classes are automatically added to your module\n- **Decorator factory**: `auto_create_decorator` generates a decorator specific to your global config\n- **Type-safe**: All generated classes are proper dataclasses with full IDE support\n\n### Field Injection Behavior\n\nWhen you use the generated decorator (e.g., `@global_pipeline_config`), the decorated class is automatically **injected as a field** into the global config class:\n\n```python\nfrom dataclasses import dataclass\nfrom lazy_config import auto_create_decorator\n\n# Create global config with auto_create_decorator\n@auto_create_decorator\n@dataclass\nclass GlobalPipelineConfig:\n    num_workers: int = 1\n\n# This creates:\n# - A decorator named `global_pipeline_config`\n# - A lazy class named `PipelineConfig`\n\n# Use the decorator on a new config class\n@global_pipeline_config\n@dataclass\nclass WellFilterConfig:\n    well_filter: str = None\n    mode: str = \"include\"\n\n# After module loading, GlobalPipelineConfig automatically has:\n# - well_filter_config: WellFilterConfig = WellFilterConfig()\n# And LazyWellFilterConfig is auto-created\n```\n\n**How it works:**\n- Decorated classes are injected as fields into `GlobalPipelineConfig`\n- Field name is snake_case of class name (e.g., `WellFilterConfig` \u2192 `well_filter_config`)\n- Lazy version is automatically created (e.g., `LazyWellFilterConfig`)\n- Injection happens at end of module loading via `_inject_all_pending_fields()`\n\nThis enables a clean, modular configuration structure where each component's config is automatically part of the global configuration.\n\n### Decorator Parameters\n\nThe generated decorator (e.g., `@global_pipeline_config`) supports optional parameters:\n\n#### `inherit_as_none` (Default: `True`)\n\nSets all inherited fields from parent classes to `None` by default, enabling proper lazy resolution:\n\n```python\n@dataclass\nclass BaseConfig:\n    timeout: int = 30\n    retries: int = 3\n\n@global_pipeline_config(inherit_as_none=True)  # Default behavior\n@dataclass\nclass ServiceConfig(BaseConfig):\n    service_name: str = \"my-service\"\n    # timeout and retries automatically set to None for lazy inheritance\n\n# This allows ServiceConfig to inherit timeout/retries from context\n# rather than using the base class defaults\n```\n\n**Why this matters:**\n- Enables polymorphic access without type-specific attribute names\n- Critical for dual-axis inheritance with multiple inheritance\n- Uses `InheritAsNoneMeta` metaclass internally\n\n#### `ui_hidden` (Default: `False`)\n\nHides configs from UI while still applying decorator behavior and keeping them in the resolution context:\n\n```python\n@global_pipeline_config(ui_hidden=True)\n@dataclass\nclass InternalConfig:\n    internal_setting: str = \"hidden\"\n    # This config won't appear in UI but is still available for inheritance\n```\n\n**Use cases:**\n- Intermediate configs that are only inherited by other configs\n- Internal implementation details not meant for user configuration\n- Base classes that should never be directly instantiated in UI\n\n### Nested Dataclass Lazification\n\nWhen creating a lazy dataclass, **nested dataclass fields are automatically converted** to their lazy versions:\n\n```python\nfrom dataclasses import dataclass\nfrom lazy_config import LazyDataclassFactory\n\n@dataclass\nclass DatabaseConfig:\n    host: str = \"localhost\"\n    port: int = 5432\n\n@dataclass\nclass AppConfig:\n    db_config: DatabaseConfig = DatabaseConfig()\n    app_name: str = \"MyApp\"\n\n# Create lazy version - nested configs are automatically lazified\nLazyAppConfig = LazyDataclassFactory.make_lazy_simple(AppConfig)\n\n# The db_config field is automatically converted to LazyDatabaseConfig\n# You don't need to manually create LazyDatabaseConfig first!\n```\n\n**Benefits:**\n- No need to manually create lazy versions of nested configs\n- Preserves field metadata (e.g., `ui_hidden` flag)\n- Creates default factories for Optional dataclass fields\n- Uses `register_lazy_type_mapping()` internally\n\n## Why lazy-config?\n\n**Before** (Manual parameter passing):\n```python\ndef process_step(data, output_dir, num_workers, debug, ...):\n    # Pass 20+ parameters through every function\n    result = sub_process(data, output_dir, num_workers, debug, ...)\n    return result\n\ndef sub_process(data, output_dir, num_workers, debug, ...):\n    # Repeat parameter declarations everywhere\n    ...\n```\n\n**After** (lazy-config):\n```python\n@dataclass\nclass StepConfig:\n    output_dir: str = None\n    num_workers: int = None\n    debug: bool = None\n\ndef process_step(data, config: LazyStepConfig):\n    # Config fields resolve automatically from context\n    print(config.output_dir)  # Resolved from context hierarchy\n    result = sub_process(data, config)\n    return result\n```\n\n## Advanced Features\n\n### Dual-Axis Inheritance\n\n```python\n# X-Axis: Context hierarchy\nwith config_context(global_config):\n    with config_context(pipeline_config):\n        with config_context(step_config):\n            # Resolves: step \u2192 pipeline \u2192 global \u2192 defaults\n            value = lazy_config.some_field\n\n# Y-Axis: Sibling inheritance (MRO-based)\n@dataclass\nclass BaseConfig:\n    field_a: str = \"base\"\n\n@dataclass\nclass SpecializedConfig(BaseConfig):\n    field_b: str = \"specialized\"\n\n# SpecializedConfig inherits field_a from BaseConfig\n```\n\n### Placeholder Generation for UI\n\n```python\nfrom lazy_config import LazyDefaultPlaceholderService\n\nservice = LazyDefaultPlaceholderService()\n\n# Generate placeholder text showing inherited values\nplaceholder = service.get_placeholder_text(\n    lazy_config,\n    \"output_dir\",\n    available_configs\n)\n# Returns: \"Inherited: /data (from GlobalConfig)\"\n```\n\n### Cache Warming\n\n```python\nfrom lazy_config import prewarm_config_analysis_cache\n\n# Pre-warm caches for faster runtime resolution\nprewarm_config_analysis_cache([GlobalConfig, PipelineConfig, StepConfig])\n```\n\n## Architecture\n\n### Dual-Axis Resolution\n\nThe framework uses pure MRO-based dual-axis resolution:\n\n**X-Axis (Context Hierarchy)**:\n```\nStep Context \u2192 Pipeline Context \u2192 Global Context \u2192 Static Defaults\n```\n\n**Y-Axis (MRO Traversal)**:\n```\nMost specific class \u2192 Least specific class (following Python's MRO)\n```\n\n**How it works:**\n1. Context hierarchy is flattened into a single `available_configs` dict\n2. For each field resolution, traverse the requesting object's MRO from most to least specific\n3. For each MRO class, check if there's a config instance in `available_configs` with a concrete (non-None) value\n4. Return the first concrete value found\n\n## Documentation\n\nFull documentation available at [lazy-config.readthedocs.io](https://lazy-config.readthedocs.io)\n\n## Requirements\n\n- Python 3.10+\n- No external dependencies (pure stdlib)\n\n## License\n\nMIT License - see LICENSE file for details\n\n## Contributing\n\nContributions welcome! Please see CONTRIBUTING.md for guidelines.\n\n## Credits\n\nDeveloped by Tristan Simas as part of the OpenHCS project.\n",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "Generic lazy dataclass configuration framework with dual-axis inheritance and contextvars-based resolution",
    "version": "0.1.0",
    "project_urls": {
        "Documentation": "https://hieraconf.readthedocs.io",
        "Homepage": "https://github.com/trissim/hieraconf",
        "Issues": "https://github.com/trissim/hieraconf/issues",
        "Repository": "https://github.com/trissim/hieraconf"
    },
    "split_keywords": [
        "configuration",
        " contextvars",
        " dataclass",
        " hierarchical",
        " inheritance",
        " lazy"
    ],
    "urls": [
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "00fb4b352e918bd0f4245b8aef677d5d405d9f6e5be462161065f9907fe2dcfa",
                "md5": "250028d448ae36fe06e68f3d2578de63",
                "sha256": "8e1121566f52ce7ef839941392834ef90afe41ebfa44ca7a7a3d95fc74e0930f"
            },
            "downloads": -1,
            "filename": "hieraconf-0.1.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "250028d448ae36fe06e68f3d2578de63",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.10",
            "size": 34968,
            "upload_time": "2025-11-01T23:14:17",
            "upload_time_iso_8601": "2025-11-01T23:14:17.242362Z",
            "url": "https://files.pythonhosted.org/packages/00/fb/4b352e918bd0f4245b8aef677d5d405d9f6e5be462161065f9907fe2dcfa/hieraconf-0.1.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "1f25c84e888ad7019ec075d0d54f62d8f685132edc03cff70a41d04fe320694b",
                "md5": "bbde2b2ddc835b8a5463006bb36b15fc",
                "sha256": "d0b278ca605f20502c9aaafb3bf268fcd55620002e7326be190f511e6413a222"
            },
            "downloads": -1,
            "filename": "hieraconf-0.1.0.tar.gz",
            "has_sig": false,
            "md5_digest": "bbde2b2ddc835b8a5463006bb36b15fc",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.10",
            "size": 50275,
            "upload_time": "2025-11-01T23:14:18",
            "upload_time_iso_8601": "2025-11-01T23:14:18.671504Z",
            "url": "https://files.pythonhosted.org/packages/1f/25/c84e888ad7019ec075d0d54f62d8f685132edc03cff70a41d04fe320694b/hieraconf-0.1.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2025-11-01 23:14:18",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "trissim",
    "github_project": "hieraconf",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "lcname": "hieraconf"
}
        
Elapsed time: 3.42378s