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.
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"andKEY='quoted value'(quotes stripped)# commentsand 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_HOST → database.host. The rule:
- Lowercase the entire name.
- Split by
_. - First segment becomes the namespace, rest joins with
_as the key. database+host→database.hostredis_cache_ttl→redis.cache_ttl
For load_env_vars(prefix="APP"):
- Strip the prefix:
APP_DATABASE_HOST→DATABASE_HOST - 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=2 → cache.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 keyget()returns value for nested dot-notation keyget()returns default when key is missingget()returnsNonewhen key is missing and no defaultget()on a namespace key returns the sub-dictset()creates a top-level keyset()creates nested key with intermediate dictsset()overwrites existing valuehas()returns True for existing keys (including None and False values)has()returns False for missing keyshas()works with dot notationforget()removes a keyforget()removes a namespace and all childrenforget()on missing key is a no-op (no error)all()returns complete nested dictall()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 dictshas()returns False if intermediate key is missing (e.g.,has("a.b.c")whenadoesn't exist)
Type Casting
string()casts int to stringstring()returns default for missing keyinteger()casts string"5432"to5432integer()returns int as-isinteger()returns default for missing keyinteger()raisesValueErrorfor non-numeric stringfloat()casts string"3.14"to3.14float()casts int3to3.0float()returns default for missing keyboolean()— True for:True,"true","TRUE","True","1","yes","on"boolean()— False for:False,"false","FALSE","0","no","off","",Noneboolean()returns default for missing keylist()splits"a,b,c"into["a", "b", "c"]list()strips whitespace from items:"a, b, c"→["a", "b", "c"]list()returns existing list as-islist()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 allos.environinto storeload_env_vars(prefix="APP")only loadsAPP_*vars- Prefix is stripped from key:
APP_DEBUG→app.debug - Key mapping:
DATABASE_HOST→database.host - Case insensitive:
DATABASE_HOSTanddatabase_hostboth 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
.envthen.env.local→.env.localwins- All sources then
set()→set()wins load_env_vars()afterload_env()→ real env vars winoverride=Falseonly fills keys not already present
Freeze
- After
freeze(),set()raisesConfigFrozenError - After
freeze(),merge()raisesConfigFrozenError - After
freeze(),forget()raisesConfigFrozenError - After
freeze(),load_dict()raisesConfigFrozenError - After
freeze(),load_env()raisesConfigFrozenError - After
freeze(),get()still works is_frozenreturnsFalsebefore freeze,Trueafter
Require
require()with all keys present does not raiserequire()with one missing key raisesMissingConfigErrorrequire()with multiple missing keys lists all of them in error messagerequire()checks dot-notation keys
File Loading
load_json()loads a valid JSON fileload_json()with missing file raisesFileNotFoundErrorload_toml()loads a valid TOML fileload_toml()withouttomllibortomliraisesImportErrorwith install instructionload_yaml()loads a valid YAML fileload_yaml()withoutpyyamlraisesImportErrorwith install instruction- Multiple file loads deep-merge correctly
Edge Cases
- Empty Config() —
get()returns default,all()returns{} - Key with
Nonevalue —get()returnsNone,has()returnsTrue - Key with
Falsevalue —get()returnsFalse,has()returnsTrue - Key with
0value —get()returns0,has()returnsTrue - Key with empty string —
get()returns"",has()returnsTrue - 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()raisesTypeError - 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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
27d62b42721759f58af1e183ff8cc0ee7524ceda28f2e84ac41bb4a53263e33d
|
|
| MD5 |
57bce6e9cf22f978df51fb8c5c35a76c
|
|
| BLAKE2b-256 |
850a75e11d2ebe9d78c2b1bb386121972a9b7afb65ca40a634a7603dbef5d900
|
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8c85abc7a0093437e4861e66f257bd1ad0fcc35d4f6e21ec910ebc7db8e765a3
|
|
| MD5 |
9f99e6674873a4d1eb8eb4af6e066cba
|
|
| BLAKE2b-256 |
5baaa2ac7e54055920ce73d84225de98c8753eeea695d891c951d21608cdf60b
|