Skip to main content

Cascading configuration with dot-notation access, env file support, and type casting.

Project description

Lucid Config

Configuration that knows where to look. Merge env vars, .env files, and config dicts with dot-notation access, type casting, and zero fuss.

Every Python project reinvents config loading. Some use os.environ everywhere. Some parse .env with python-dotenv and hope for the best. Some write a settings.py that turns into a 400-line monster. Then testing becomes a nightmare because config is scattered across module-level globals. Lucid Config gives you one clean layer that merges all your sources, casts types, and plugs straight into the Lucid Container.

PyPI version Python 3.10+ License: MIT


Before & After

Without Lucid Config:

import os
from dotenv import load_dotenv

load_dotenv()

DB_HOST = os.environ.get("DB_HOST", "localhost")
DB_PORT = int(os.environ.get("DB_PORT", "5432"))
DEBUG = os.environ.get("DEBUG", "false").lower() in ("true", "1", "yes")
CACHE_DRIVER = os.environ.get("CACHE_DRIVER", "memory")
REDIS_HOST = os.environ.get("REDIS_HOST", "localhost")
REDIS_PORT = int(os.environ.get("REDIS_PORT", "6379"))

# Scattered across files, no nesting, manual casting everywhere,
# testing means monkeypatching os.environ or reloading modules

With Lucid Config:

from lucid_config import Config

config = Config()
config.load_env(".env")
config.load_dict({
    "database": {"host": "localhost", "port": 5432},
    "cache": {"driver": "memory", "redis": {"host": "localhost", "port": 6379}},
    "app": {"debug": False},
})

config.get("database.host")              # "localhost" (or DB_HOST from env)
config.integer("database.port")          # 5432
config.boolean("app.debug")             # False
config.get("cache.redis.host")          # "localhost"

# In tests — swap the whole thing, no monkeypatching
test_config = Config({"database.host": "test-db", "app.debug": True})
container.instance(ConfigContract, test_config)

Installation

pip install lucid-config

Requires Python 3.10 or higher. No dependencies.


Quick Start

From a dict

from lucid_config import Config

config = Config({
    "app": {
        "name": "my-project",
        "debug": False,
        "port": 8000,
    },
    "database": {
        "host": "localhost",
        "port": 5432,
        "name": "mydb",
    },
})

config.get("app.name")             # "my-project"
config.get("app.version", "0.0.1") # "0.0.1" (default)
config.integer("app.port")         # 8000

From environment variables

# Reads DATABASE_HOST, DATABASE_PORT, APP_DEBUG, etc.
config = Config()
config.load_env_prefix("APP")

# APP_NAME=my-project  →  config.get("app.name")  →  "my-project"
# APP_DEBUG=true       →  config.boolean("app.debug")  →  True

From a .env file

config = Config()
config.load_env(".env")
config.load_env(".env.local")  # overrides .env

Source Priority

When the same key exists in multiple sources, the last loaded source wins. The intended load order (lowest to highest priority):

1. Default dict          (base config, committed to repo)
2. .env file             (shared defaults, committed or gitignored)
3. .env.local file       (personal overrides, always gitignored)
4. Environment variables (deployment overrides, set by infrastructure)
5. Runtime overrides     (set() calls in code)
config = Config()

# 1. Base defaults
config.load_dict({
    "cache": {"driver": "memory"},
    "app": {"debug": False},
})

# 2-3. Env files (later files override earlier ones)
config.load_env(".env")
config.load_env(".env.local")

# 4. Real environment variables (highest priority from files)
config.load_env_vars()

# 5. Runtime override (highest of all)
config.set("app.debug", True)

At this point, if APP_DEBUG=false is in the real environment, config.get("app.debug") returns "false" (the env var overrides .env). But the config.set() call overrides everything.


Full API Reference

Config(initial=None)

Creates a new config repository, optionally pre-loaded with a dict.

Parameter Type Description
initial dict | None Optional nested dict of default values.
config = Config()
config = Config({"app": {"name": "my-project"}})

.get(key, default=None)

Retrieves a value using dot notation. Returns default if the key doesn't exist.

Parameter Type Description
key str Dot-separated path (e.g., "database.host").
default Any Returned if key is not found.
config.get("database.host")                  # "localhost"
config.get("database.options.timeout", 30)   # 30 (not set)
config.get("database")                       # {"host": "localhost", "port": 5432, ...}

Getting a key that points to a nested dict returns the entire sub-dict.


.set(key, value)

Sets a value at the given dot-notation path. Creates intermediate dicts as needed.

config.set("database.host", "prod-db.example.com")
config.set("services.payment.timeout", 60)  # creates services.payment dict

.has(key)

Returns True if the key exists (even if its value is None or False).

config.has("database.host")    # True
config.has("database.color")   # False

.all()

Returns the entire config as a nested dict.

everything = config.all()
# {"app": {"name": "my-project", ...}, "database": {...}, ...}

.forget(key)

Removes a key and its value. If the key points to a nested dict, the entire subtree is removed.

config.forget("database.options")
config.has("database.options")  # False

.merge(data)

Deep-merges a dict into the existing config. Nested dicts are merged recursively, not replaced.

config = Config({"database": {"host": "localhost", "port": 5432}})
config.merge({"database": {"port": 3306, "name": "newdb"}})

config.get("database.host")  # "localhost" (preserved)
config.get("database.port")  # 3306 (overwritten)
config.get("database.name")  # "newdb" (added)

Type Casting Methods

All casting methods accept key and an optional default. They retrieve the raw value and cast it.

.string(key, default="")

Casts to str.

config.string("app.name")             # "my-project"
config.string("app.port")             # "8000" (int → str)

.integer(key, default=0)

Casts to int. Handles string values from env vars.

config.integer("database.port")       # 5432
config.integer("database.port")       # 5432 (even if stored as "5432" from env)
config.integer("missing.key", 3000)   # 3000

.float(key, default=0.0)

Casts to float.

config.float("app.rate_limit", 1.5)   # 1.5

.boolean(key, default=False)

Casts to bool. Recognizes common truthy/falsy strings from env vars.

  • Truthy: True, "true", "True", "TRUE", "1", "yes", "on"
  • Falsy: False, "false", "False", "FALSE", "0", "no", "off", "", None
# APP_DEBUG=true in environment
config.boolean("app.debug")           # True
config.boolean("app.verbose", False)   # False (missing, uses default)

.list(key, default=None, delimiter=",")

Splits a string value into a list. If the value is already a list, returns it directly.

# ALLOWED_HOSTS=localhost,example.com,api.example.com in env
config.list("app.allowed_hosts")
# ["localhost", "example.com", "api.example.com"]

# Already a list in dict config
config.set("app.cors_origins", ["http://localhost:3000"])
config.list("app.cors_origins")
# ["http://localhost:3000"]

Environment Loading

.load_env(path, override=True)

Parses a .env file and loads its values into the config store.

Parameter Type Description
path str Path to the .env file.
override bool If True, values override existing keys.

Supports:

  • KEY=value (basic)
  • KEY="quoted value" and KEY='quoted value' (quotes stripped)
  • # comments and blank lines (ignored)
  • export KEY=value (export prefix stripped)
  • Inline comments: KEY=value # this is ignored
  • Multiline values with " or ' quotes
  • Variable interpolation: URL=https://${HOST}:${PORT} (resolves from already-loaded values and real env vars)

.env files do NOT pollute os.environ. Values are loaded into the config store only. Use .load_env_vars() if you need real environment variable overrides.

config.load_env(".env")
config.load_env(".env.local")          # overrides .env values
config.load_env(".env.test", override=False)  # only fills missing keys

Key mapping: Environment variable names are converted to dot-notation config keys by lowercasing and replacing _ with . for the first level. DATABASE_HOST becomes database.host. APP_DEBUG becomes app.debug. REDIS_CACHE_TTL becomes redis.cache_ttl.

The mapping rule: the first underscore is the namespace separator (becomes .), remaining underscores are preserved. This is configurable via .load_env(path, separator="_", depth=1).


.load_env_vars(prefix=None)

Loads values from real os.environ into the config store.

Parameter Type Description
prefix str | None If set, only loads vars starting with this prefix.
# Load everything
config.load_env_vars()

# Load only vars starting with APP_
config.load_env_vars(prefix="APP")
# APP_NAME → config.get("app.name")
# APP_DEBUG → config.boolean("app.debug")
# DATABASE_HOST is ignored

.load_env_prefix(prefix)

Shorthand for load_env_vars(prefix=prefix). Reads real environment variables with the given prefix.

config.load_env_prefix("MYAPP")
# MYAPP_DB_HOST=localhost → config.get("myapp.db_host")

Loading from Files

.load_dict(data)

Loads a nested dict. Same as passing initial to the constructor, but can be called multiple times. Later calls override earlier values (deep merge).

config.load_dict({"app": {"name": "my-project"}})
config.load_dict({"app": {"version": "1.0"}})

config.get("app.name")     # "my-project" (preserved)
config.get("app.version")  # "1.0" (added)

.load_json(path)

Loads config from a JSON file.

config.load_json("config/app.json")

.load_toml(path)

Loads config from a TOML file. Uses tomllib (stdlib in Python 3.11+, falls back to tomli for 3.10).

config.load_toml("config/app.toml")

.load_yaml(path)

Loads config from a YAML file. Requires pyyaml to be installed — raises ImportError with a clear message if missing.

config.load_yaml("config/app.yaml")

Immutability

.freeze()

Makes the config read-only. Any subsequent .set(), .merge(), .forget(), or .load_*() calls raise ConfigFrozenError.

config.freeze()
config.set("app.name", "nope")  # raises ConfigFrozenError

Useful after boot to prevent accidental mutations during request handling.

.is_frozen

Property that returns True if the config is frozen.


Required Keys

.require(*keys)

Validates that all specified keys exist. Raises MissingConfigError with a list of all missing keys (not just the first one).

config.require("database.host", "database.name", "app.secret_key")
# Raises: MissingConfigError: Missing required config keys: database.name, app.secret_key

Call this after loading all sources but before the app starts handling requests.


Container Integration

ConfigContract

from abc import ABC, abstractmethod
from typing import Any

class ConfigContract(ABC):
    @abstractmethod
    def get(self, key: str, default: Any = None) -> Any: ...

    @abstractmethod
    def set(self, key: str, value: Any) -> None: ...

    @abstractmethod
    def has(self, key: str) -> bool: ...

    @abstractmethod
    def integer(self, key: str, default: int = 0) -> int: ...

    @abstractmethod
    def boolean(self, key: str, default: bool = False) -> bool: ...

    @abstractmethod
    def string(self, key: str, default: str = "") -> str: ...

    @abstractmethod
    def all(self) -> dict: ...

ConfigServiceProvider

from lucid_container import ServiceProvider
from lucid_config import Config, ConfigContract

class ConfigServiceProvider(ServiceProvider):

    def register(self):
        self.app.singleton(ConfigContract, lambda c: self._create_config())
        self.app.alias("config", ConfigContract)

    def _create_config(self):
        config = Config()

        # Load in priority order
        config.load_dict(self._load_defaults())
        config.load_env(".env")
        config.load_env(".env.local")
        config.load_env_vars()

        # Validate essentials
        config.require("app.name", "app.secret_key")

        return config

    def _load_defaults(self):
        return {
            "app": {"name": "lucid-app", "debug": False, "port": 8000},
            "database": {"host": "localhost", "port": 5432},
            "cache": {"driver": "memory"},
        }

Usage in other providers

class CacheServiceProvider(ServiceProvider):

    def register(self):
        self.app.singleton(CacheContract, lambda c: self._create_cache(c))

    def _create_cache(self, container):
        # ConfigContract is available because ConfigServiceProvider registered first
        config = container.make(ConfigContract)
        driver = config.get("cache.driver", "memory")

        if driver == "redis":
            return RedisCache(
                host=config.get("cache.redis.host", "localhost"),
                port=config.integer("cache.redis.port", 6379),
            )
        return InMemoryCache()

This is the pattern every future Lucid package follows: read config through the contract, never touch os.environ directly.


Real-World Examples

Typical project setup

my-project/
├── .env                 # shared defaults (committed or gitignored, team preference)
├── .env.local           # personal overrides (always gitignored)
├── config/
│   └── defaults.toml    # structured defaults
└── app.py

.env:

APP_NAME=my-project
APP_DEBUG=false
DATABASE_HOST=localhost
DATABASE_PORT=5432
DATABASE_NAME=mydb
CACHE_DRIVER=memory

.env.local:

APP_DEBUG=true
DATABASE_HOST=dev-db.local

app.py:

from lucid_config import Config

config = Config()
config.load_toml("config/defaults.toml")
config.load_env(".env")
config.load_env(".env.local")
config.load_env_vars()

config.require("app.name", "database.host", "database.name")
config.freeze()

print(config.get("app.name"))            # "my-project"
print(config.boolean("app.debug"))       # True (from .env.local)
print(config.get("database.host"))       # "dev-db.local" (from .env.local)
print(config.integer("database.port"))   # 5432

Testing

def test_user_creation():
    test_config = Config({
        "database": {"host": "localhost", "port": 5432, "name": "test_db"},
        "app": {"debug": True, "secret_key": "test-secret"},
        "mail": {"driver": "log"},
    })

    container = Container()
    container.instance(ConfigContract, test_config)
    container.register_provider(DatabaseServiceProvider)
    container.boot()

    # Everything uses the test config — no env vars, no .env files, no monkeypatching
    service = container.make(UserService)
    user = service.create("alice@test.com")
    assert user.email == "alice@test.com"

Per-environment config files

import os

config = Config()
config.load_toml("config/defaults.toml")

env = os.environ.get("APP_ENV", "development")
config.load_toml(f"config/{env}.toml")    # config/production.toml, config/staging.toml, etc.

config.load_env(".env")
config.load_env(f".env.{env}")            # .env.production, .env.staging, etc.
config.load_env_vars()

Architecture

Project Structure

lucid-config/
├── src/
│   └── lucid_config/
│       ├── __init__.py          # Public API exports
│       ├── config.py            # Config class
│       ├── contract.py          # ConfigContract ABC
│       ├── env_parser.py        # .env file parser
│       ├── key_mapper.py        # ENV_VAR_NAME → dot.notation mapping
│       └── exceptions.py        # MissingConfigError, ConfigFrozenError
├── tests/
│   ├── __init__.py
│   ├── test_config.py           # Core get/set/has/forget/all
│   ├── test_dot_notation.py     # Nested key access and creation
│   ├── test_type_casting.py     # string, integer, float, boolean, list
│   ├── test_env_parser.py       # .env file parsing (comments, quotes, interpolation)
│   ├── test_env_vars.py         # os.environ loading with prefix
│   ├── test_key_mapping.py      # ENV_VAR → dot.notation conversion
│   ├── test_merge.py            # Deep merge behavior
│   ├── test_source_priority.py  # Override order across sources
│   ├── test_freeze.py           # Immutability after freeze()
│   ├── test_require.py          # Required key validation
│   ├── test_file_loading.py     # JSON, TOML, YAML loading
│   └── test_edge_cases.py       # Empty config, None values, deeply nested keys
├── pyproject.toml
├── README.md
├── LICENSE
└── CHANGELOG.md

Implementation Notes

Config internals:

The config stores everything in a single nested dict (self._store: dict). Dot-notation access walks the dict by splitting on .:

def get(self, key: str, default=None):
    keys = key.split(".")
    current = self._store

    for k in keys:
        if isinstance(current, dict) and k in current:
            current = current[k]
        else:
            return default

    return current

.set() works in reverse — walks the dict creating intermediate dicts as needed, then sets the final value.

Source layering:

There is no separate "layer" system. Each load_*() call deep-merges into the same _store dict. Later loads overwrite earlier values for the same key. This keeps the implementation simple and the mental model clear: last write wins.

Env file parser:

The .env parser is a standalone module (env_parser.py) that handles:

# Basic
KEY=value

# Quoted (quotes stripped, preserves inner whitespace)
KEY="hello world"
KEY='hello world'

# Comments and blanks
# This is a comment
  # Indented comment

# Export prefix (stripped)
export KEY=value

# Inline comments (only outside quotes)
KEY=value  # this part is ignored

# Multiline with quotes
KEY="line one
line two
line three"

# Variable interpolation (from already-loaded values and os.environ)
BASE_URL=https://${HOST}:${PORT}/api

The parser returns a flat dict[str, str]. All values are strings — type casting happens in the Config methods.

Key mapping from env vars:

DATABASE_HOSTdatabase.host. The rule:

  1. Lowercase the entire name.
  2. Split by _.
  3. First segment becomes the namespace, rest joins with _ as the key.
  4. database + hostdatabase.host
  5. redis_cache_ttlredis.cache_ttl

For load_env_vars(prefix="APP"):

  1. Strip the prefix: APP_DATABASE_HOSTDATABASE_HOST
  2. Then apply the same mapping: database.host

This is configurable: load_env(path, depth=2) would treat the first two underscore-separated segments as namespace levels. CACHE_REDIS_HOST with depth=2cache.redis.host.

TOML loading (Python 3.10 compatibility):

def load_toml(self, path: str):
    try:
        import tomllib  # Python 3.11+
    except ImportError:
        try:
            import tomli as tomllib  # Fallback for 3.10
        except ImportError:
            raise ImportError(
                "TOML support requires Python 3.11+ or the 'tomli' package. "
                "Install it with: pip install tomli"
            )
    with open(path, "rb") as f:
        data = tomllib.load(f)
    self.merge(data)

Same pattern for YAML — try import yaml, raise clear error if missing.

Deep merge algorithm:

def _deep_merge(self, base: dict, override: dict) -> dict:
    for key, value in override.items():
        if key in base and isinstance(base[key], dict) and isinstance(value, dict):
            self._deep_merge(base[key], value)
        else:
            base[key] = value
    return base

Dicts are merged recursively. Non-dict values (strings, ints, lists) are replaced entirely.

Public API (what __init__.py exports)

from lucid_config.config import Config
from lucid_config.contract import ConfigContract
from lucid_config.exceptions import MissingConfigError, ConfigFrozenError

__all__ = [
    "Config",
    "ConfigContract",
    "MissingConfigError",
    "ConfigFrozenError",
]

Exceptions

Exception When
MissingConfigError .require() finds missing keys. Message lists all of them.
ConfigFrozenError Mutation attempted after .freeze().

pyproject.toml Specification

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[project]
name = "lucid-config"
version = "0.1.0"
description = "Cascading configuration with dot-notation access, env file support, and type casting."
readme = "README.md"
license = "MIT"
requires-python = ">=3.10"
authors = [
    { name = "Sharik Shaikh", email = "shaikhsharik709@gmail.com" },
]
keywords = [
    "config", "configuration", "settings", "dotenv", "env",
    "environment", "toml", "yaml", "type-casting",
]
classifiers = [
    "Development Status :: 4 - Beta",
    "Intended Audience :: Developers",
    "License :: OSI Approved :: MIT License",
    "Programming Language :: Python :: 3",
    "Programming Language :: Python :: 3.10",
    "Programming Language :: Python :: 3.11",
    "Programming Language :: Python :: 3.12",
    "Programming Language :: Python :: 3.13",
    "Topic :: Software Development :: Libraries :: Python Modules",
    "Typing :: Typed",
]

[project.urls]
Homepage = "https://github.com/sharik709/lucid-config"
Documentation = "https://github.com/sharik709/lucid-config#readme"
Repository = "https://github.com/sharik709/lucid-config"
Issues = "https://github.com/sharik709/lucid-config/issues"

[tool.pytest.ini_options]
testpaths = ["tests"]

[tool.mypy]
strict = true

[project.optional-dependencies]
dev = ["pytest>=7.0", "mypy>=1.0", "ruff>=0.1"]
toml = ["tomli>=2.0;python_version<'3.11'"]
yaml = ["pyyaml>=6.0"]
all = ["tomli>=2.0;python_version<'3.11'", "pyyaml>=6.0"]

Test Cases to Implement

Core (get / set / has / forget / all)

  • get() returns value for top-level key
  • get() returns value for nested dot-notation key
  • get() returns default when key is missing
  • get() returns None when key is missing and no default
  • get() on a namespace key returns the sub-dict
  • set() creates a top-level key
  • set() creates nested key with intermediate dicts
  • set() overwrites existing value
  • has() returns True for existing keys (including None and False values)
  • has() returns False for missing keys
  • has() works with dot notation
  • forget() removes a key
  • forget() removes a namespace and all children
  • forget() on missing key is a no-op (no error)
  • all() returns complete nested dict
  • all() returns a copy, not a reference to internal store

Dot Notation

  • Single-level key: get("name")
  • Two-level key: get("database.host")
  • Three-level key: get("cache.redis.host")
  • Deeply nested (5+ levels) works
  • set() with dot notation creates missing intermediate dicts
  • has() returns False if intermediate key is missing (e.g., has("a.b.c") when a doesn't exist)

Type Casting

  • string() casts int to string
  • string() returns default for missing key
  • integer() casts string "5432" to 5432
  • integer() returns int as-is
  • integer() returns default for missing key
  • integer() raises ValueError for non-numeric string
  • float() casts string "3.14" to 3.14
  • float() casts int 3 to 3.0
  • float() returns default for missing key
  • boolean() — True for: True, "true", "TRUE", "True", "1", "yes", "on"
  • boolean() — False for: False, "false", "FALSE", "0", "no", "off", "", None
  • boolean() returns default for missing key
  • list() splits "a,b,c" into ["a", "b", "c"]
  • list() strips whitespace from items: "a, b, c"["a", "b", "c"]
  • list() returns existing list as-is
  • list() uses custom delimiter: list("key", delimiter="|")
  • list() returns default for missing key

Env File Parser

  • Parses KEY=value
  • Parses KEY="quoted value" (strips quotes)
  • Parses KEY='single quoted'
  • Ignores blank lines
  • Ignores comment lines (# comment)
  • Handles export KEY=value (strips export)
  • Handles inline comments: KEY=value # comment
  • Does NOT treat # inside quotes as comment
  • Handles multiline values in double quotes
  • Handles multiline values in single quotes
  • Variable interpolation: ${OTHER_KEY} resolved from loaded values
  • Variable interpolation: ${MISSING} left as empty string or raises
  • File not found raises FileNotFoundError
  • Empty file produces empty dict
  • Values are NOT written to os.environ

Environment Variable Loading

  • load_env_vars() loads all os.environ into store
  • load_env_vars(prefix="APP") only loads APP_* vars
  • Prefix is stripped from key: APP_DEBUGapp.debug
  • Key mapping: DATABASE_HOSTdatabase.host
  • Case insensitive: DATABASE_HOST and database_host both map correctly
  • Variables override previously loaded values

Merge

  • Merging adds new top-level keys
  • Merging adds new nested keys without removing siblings
  • Merging overwrites existing leaf values
  • Merging two nested dicts recursively merges
  • Merging a dict over a non-dict replaces it
  • Merging a non-dict over a dict replaces it
  • load_dict() deep-merges into store

Source Priority

  • Dict loaded first, env file second → env file wins
  • Env file loaded first, dict loaded second → dict wins
  • .env then .env.local.env.local wins
  • All sources then set()set() wins
  • load_env_vars() after load_env() → real env vars win
  • override=False only fills keys not already present

Freeze

  • After freeze(), set() raises ConfigFrozenError
  • After freeze(), merge() raises ConfigFrozenError
  • After freeze(), forget() raises ConfigFrozenError
  • After freeze(), load_dict() raises ConfigFrozenError
  • After freeze(), load_env() raises ConfigFrozenError
  • After freeze(), get() still works
  • is_frozen returns False before freeze, True after

Require

  • require() with all keys present does not raise
  • require() with one missing key raises MissingConfigError
  • require() with multiple missing keys lists all of them in error message
  • require() checks dot-notation keys

File Loading

  • load_json() loads a valid JSON file
  • load_json() with missing file raises FileNotFoundError
  • load_toml() loads a valid TOML file
  • load_toml() without tomllib or tomli raises ImportError with install instruction
  • load_yaml() loads a valid YAML file
  • load_yaml() without pyyaml raises ImportError with install instruction
  • Multiple file loads deep-merge correctly

Edge Cases

  • Empty Config() — get() returns default, all() returns {}
  • Key with None value — get() returns None, has() returns True
  • Key with False value — get() returns False, has() returns True
  • Key with 0 value — get() returns 0, has() returns True
  • Key with empty string — get() returns "", has() returns True
  • Key with dots in the VALUE (not key) — value preserved as-is
  • Setting a value to a dict — subsequent dot-notation access works
  • Non-string key in .get() raises TypeError
  • Very long dot paths (10+ levels) work
  • Unicode keys and values work
  • all() doesn't expose internal store (mutation safety)

Publishing to PyPI

pip install build twine
python -m build
twine upload --repository testpypi dist/*
pip install --index-url https://test.pypi.org/simple/ lucid-config
twine upload dist/*

Part of the Lucid Ecosystem

Lucid Config is the third package in the Lucid ecosystem — the bridge between your environment and every other Lucid package.

Released:

  • lucid-pipeline — Clean, expressive pipelines for multi-step data processing.
  • lucid-container — Dependency injection container with autowiring.

Coming soon:

  • lucid-cache — Multi-driver cache with a unified API.
  • lucid-events — Event dispatcher with listeners and subscribers.

License

MIT License. See LICENSE for details.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

lucid_config-0.1.0.tar.gz (26.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

lucid_config-0.1.0-py3-none-any.whl (16.0 kB view details)

Uploaded Python 3

File details

Details for the file lucid_config-0.1.0.tar.gz.

File metadata

  • Download URL: lucid_config-0.1.0.tar.gz
  • Upload date:
  • Size: 26.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.11.11

File hashes

Hashes for lucid_config-0.1.0.tar.gz
Algorithm Hash digest
SHA256 27d62b42721759f58af1e183ff8cc0ee7524ceda28f2e84ac41bb4a53263e33d
MD5 57bce6e9cf22f978df51fb8c5c35a76c
BLAKE2b-256 850a75e11d2ebe9d78c2b1bb386121972a9b7afb65ca40a634a7603dbef5d900

See more details on using hashes here.

File details

Details for the file lucid_config-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: lucid_config-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 16.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.11.11

File hashes

Hashes for lucid_config-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 8c85abc7a0093437e4861e66f257bd1ad0fcc35d4f6e21ec910ebc7db8e765a3
MD5 9f99e6674873a4d1eb8eb4af6e066cba
BLAKE2b-256 5baaa2ac7e54055920ce73d84225de98c8753eeea695d891c951d21608cdf60b

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page