Skip to main content

Simple cross-platform settings/configuration package.

Project description

CrossConfig

This package provides a simple cross-platform configuration system for storing, loading, and updating settings for an application or project. This package uses only standard library modules, and it encodes settings in JSON.

Installation

pip install crossconfig

Status

Issues can be tracked here. Changelog can be found here

Usage

Usage is simple: import the get_config function, and call it with the name of your application. This will return a config object that you can use to load, save, and get settings. If you want to use a portable config, pass portable=True to the function, and the settings file will be stored in the current working directory instead of the user's home directory. get_config returns a singleton, so unless it is called with replace=True, all invocations with the same params will access the same object. This means that the config object does not need to be passed around an application -- just use get_config wherever it is needed and call config.load() during app initialization.

Full documentation generated by autodox can be found here.

from crossconfig import get_config

# choose whether to load the config for the current user or a portable config
portable = True

# load the config
config = get_config("my_app_name", portable=portable)
config.load()

# get a path for a subdirectory
subdir_path = config.path("subdir")

# set some settings
config.set("my_setting", "my_value")
config.set(["database", "host"], "localhost")
config.set(["database", "port"], 5432)
config.set(["ui", "theme", "dark"], True)

# save and reload the config
config.save()
[config.unset(key) for key in config.list()]
assert len(config.list()) == 0
config.load()
assert len(config.list()) == 3

# get the settings
assert config.get("my_setting") == "my_value"
assert config.get(["database", "host"]) == "localhost"
assert config.get("ui") == {"theme": {"dark": True}}

# Missing keys return None (or your default)
config.unset("ui")
assert config.get(["ui", "theme"], "default") == "default"

Values can be any JSON-compatible type: bool, str, int, float, list, or dict.

Event System

The config object supports a publish/subscribe event system for reacting to configuration changes. Automatic events include the following: ('set', *key), ('unset', *key), 'save', and 'load'.

Basic Event Subscription

You can subscribe to automatic events that fire when settings are set, unset, saved, or loaded:

from crossconfig import get_config

config = get_config("my_app")

# Define a listener function
def on_setting_change(event, data):
    print(f"Event: {event}, Data: {data}")

# Subscribe to a specific key being set/unset
config.subscribe(("set", "theme"), on_setting_change)
config.subscribe(("unset", "theme"), on_setting_change)

# Set/unset a setting - this will trigger the listener
config.set("theme", "dark")  # Prints: Event: ('set', 'theme'), Data: dark
config.unset("theme")  # Prints: Event: ('unset', 'theme'), Data: None

# Subscribe to file operation events
config.subscribe("load", on_setting_change)
config.subscribe("save", on_setting_change)

# Save/load config - triggers the listener
config.save()  # Event: save, Data: None
config.load()  # Event: load, Data: {...settings...}

# Unsubscribe from an event
config.unsubscribe(("set", "theme"), on_setting_change)
Wildcard Subscriptions

Wildcards let you subscribe to multiple events at once:

config = get_config("my_app")

# Subscribe to ALL set/unset events
config.subscribe(("set", "*"), lambda e, d: print(f"Setting changed: {e}"))
config.subscribe(("unset", "*"), lambda e, d: print(f"Setting removed: {e}"))

# Subscribe to ANY event on a specific key (set or unset)
config.subscribe(("*", "theme"), lambda e, d: print(f"Theme changed: {e}, {d}"))

# Subscribe to ALL events (wildcard of wildcards)
config.subscribe("*", lambda e, d: print(f"Any event: {e}"))

config.set("theme", "dark")
config.set("language", "en")
config.unset("theme")
Hierarchical Event Bubbling

Nested events bubble to all parent listeners:

config = get_config("my_app")

# Listen to all changes under "database" section
config.subscribe(("*", "database"), lambda e, d: print(f"DB: {e}"))

# This fires the listener (nested under "database")
config.set(["database", "host"], "localhost")  # DB: ('set', 'database', 'host')
config.set(["database", "port"], 5432)  # DB: ('set', 'database', 'port')

# This also fires the listener (direct change to "database")
config.set("database", {"host": "localhost"})  # DB: ('set', 'database')
Custom Event Publishing & Unsubscribing

You can publish custom events and remove subscriptions:

config = get_config("my_app")

listener = lambda e, d: print(f"Event: {e}, Data: {d}")

# Subscribe to a custom event or event family
config.subscribe("custom_event", listener)
config.subscribe(("custom_event",), listener)
config.subscribe(("*", "scope"), listener)

# Publish a custom event
config.publish("custom_event", {"message": "hello"})
# Prints: Event: custom_event, Data: {'message': 'hello'}

# Hiearchical events work with the ("custom_event",) subscription
config.publish(("custom_event", "something"), "secret message")
# Prints: Event: ('custom_event', 'something'), Data: secret message

# Wildstar ("*", "scope") listener also works hierarchically
config.publish(("verb", "scope", "thing"), 123)
# Prints: Event: ('verb', 'scope', 'thing'), Data: 123

# Unsubscribe the listener
config.unsubscribe("custom_event", listener)

# Publishing now won't trigger the listener
config.publish("custom_event", {"message": "hello again"})

Note: String and single-element tuple events trigger each other's subscribers. Subscribing to "custom" is triggered by both publish("custom") and publish(("custom",)). Subscribing to ("custom",) is triggered by both publish(("custom",)) and publish("custom"). Multi-element tuples like ("event", "child") do not trigger string subscribers.

Configuring Error Handling and Logging

By default, exceptions raised by event listeners are silently suppressed. This prevents one failing listener from stopping execution or other listeners from running. You can configure this behavior and add logging for listener errors:

config = get_config("my_app")

# Disable error suppression (listeners can raise exceptions)
config.set_suppress_listener_errors(False)

# Add logging for listener errors
import logging
logger = logging.getLogger("my_app")
config.set_logger(logger)

With a logger configured, listener errors are logged with exception details:

def failing_listener(event, data):
    raise ValueError("Something went wrong")

config.subscribe("custom", failing_listener)
config.publish("custom", "data")
# Logs: ERROR:my_app:Listener failed for event custom
# Traceback (most recent call last):
#   ...
# ValueError: Something went wrong

Logging and error suppression can be enabled or disabled independently.

Notes

  • The load method will return a JSON decode error if the config file is not valid JSON. If it loads successfully, it will return None.
  • The load method publishes a 'load' event with the loaded settings or a JSONDecodeError on failure.
  • The save method publishes a 'save' event after writing to file.
  • There is no lock for multi-threaded access to the config object or file. Calling save or load in a multi-threaded environment may result in a race condition.
  • If a setting is set in one instance of the config object, it will be reflected in all other instances of the config object retrieved with the same call to get_config within the same process.
  • This may not follow the Windows- or MacOS-specific conventions for the current year. The goal is to make something that works regardless. However, if the behavior of os.expanduser or os.getcwd changes, this may break in the future.

AI Agent Skill

This package includes a CLI command for exporting an AI coding agent skill. This helps AI coding assistants like OpenCode, Claude Code, Cursor, and Codex use the library correctly.

CLI Usage
# Print the skill to stdout
crossconfig skill

# Export to a custom directory
crossconfig skill -O ./my-skills

# Install for specific AI coding harnesses
crossconfig opencode    # → .opencode/skills/crossconfig/SKILL.md
crossconfig claude      # → .claude/skills/crossconfig/SKILL.md
crossconfig cursor      # → .cursor/skills/crossconfig/SKILL.md
crossconfig codex       # → .agents/skills/crossconfig/SKILL.md

The skill provides AI agents with version-locked knowledge of CrossConfig's API, platform detection, nested configuration, event system, and best practices. Restart your AI coding harness after installation to make the skill available.

More Resources

Check out the Pycelium discord server. If you experience a problem, please discuss it on the Discord server. All suggestions for improvement are also welcome, and the best place for that is also Discord. If you do not use Discord, open an issue or discussion thread on Github.

Testing

To test, clone the repo and then execute the test files.

On Windows:

python tests\test_base.py
python tests\test_windows.py

On civilized OSes:

find tests/ -name test_*.py -print -exec python {} \;

Testing suites are platform-specific, but the tests that should not run on a given platform will be skipped if their files are run.

There are a total of 46 tests: 36 tests of the base class methods; 5 tests for POSIX systems; and 5 tests for Windows.

(Platform-dependent test suites only run on the appropriate platforms.)

License

Copyright (c) 2026 Jonathan Voss (k98kurz)

Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

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

crossconfig-0.0.8.tar.gz (14.6 kB view details)

Uploaded Source

Built Distribution

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

crossconfig-0.0.8-py3-none-any.whl (14.0 kB view details)

Uploaded Python 3

File details

Details for the file crossconfig-0.0.8.tar.gz.

File metadata

  • Download URL: crossconfig-0.0.8.tar.gz
  • Upload date:
  • Size: 14.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.3

File hashes

Hashes for crossconfig-0.0.8.tar.gz
Algorithm Hash digest
SHA256 cb3992e98fdd5c456ffc2bd879597bd9123ee5a586b2ea30ab20cb8062c16892
MD5 5606db88dc9252f72df43179bf50b3e8
BLAKE2b-256 510afafbc06affe376693ca7f8fdf4a0f0b8b6fcc43368eb52f018c63027ca67

See more details on using hashes here.

File details

Details for the file crossconfig-0.0.8-py3-none-any.whl.

File metadata

  • Download URL: crossconfig-0.0.8-py3-none-any.whl
  • Upload date:
  • Size: 14.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.3

File hashes

Hashes for crossconfig-0.0.8-py3-none-any.whl
Algorithm Hash digest
SHA256 8c65c8c2800dc2b4b58809250c30c1a087bedfc618832ec689597929e83b4e95
MD5 51e298335e66836d47a56dd23a569448
BLAKE2b-256 97f4a5a2bd282e6faecd732c51ea0580acdc48bc7ce3955be44e04f00f8234f3

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