From 3bbcdaa3fa4902c818b56c19587d1eade6157364 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 10 Jan 2026 16:23:33 +0000 Subject: [PATCH 1/5] Add config-utils CLI tool project - Create new uvx-compatible CLI tool for capturing configurations - Implement capture-env command to export environment variables to YAML - Implement capture-django-settings command to export Django settings to YAML - Add comprehensive README with installation and usage instructions - Include project structure with pyproject.toml for proper packaging --- config-utils/README.md | 150 ++++++++++++++++++++++++++ config-utils/config_utils/__init__.py | 3 + config-utils/config_utils/cli.py | 137 +++++++++++++++++++++++ config-utils/pyproject.toml | 23 ++++ 4 files changed, 313 insertions(+) create mode 100644 config-utils/README.md create mode 100644 config-utils/config_utils/__init__.py create mode 100644 config-utils/config_utils/cli.py create mode 100644 config-utils/pyproject.toml diff --git a/config-utils/README.md b/config-utils/README.md new file mode 100644 index 0000000..e1c76af --- /dev/null +++ b/config-utils/README.md @@ -0,0 +1,150 @@ +# config-utils + +A CLI tool for capturing environment variables and Django settings in YAML format. + +## Features + +- **capture-env**: Capture all environment variables and export them to YAML +- **capture-django-settings**: Capture Django project settings and export them to YAML + +## Installation + +### Using uvx (Recommended) + +Run the tool directly without installation: + +```bash +uvx --from . config-utils capture-env +``` + +### Using pip + +Install from the local directory: + +```bash +pip install . +``` + +Or install in editable mode for development: + +```bash +pip install -e . +``` + +### For Django support + +Install with Django optional dependencies: + +```bash +pip install ".[django]" +``` + +## Usage + +### Capture Environment Variables + +Capture all environment variables to a YAML file: + +```bash +config-utils capture-env +``` + +This will create `env_config.yaml` with all your environment variables. + +#### Options + +- `-o, --output PATH`: Specify output file path (default: `env_config.yaml`) +- `-f, --format`: Output format, yaml or yml (default: `yaml`) + +#### Examples + +```bash +# Capture to custom file +config-utils capture-env -o my_env.yaml + +# Capture with yml extension +config-utils capture-env -o config.yml -f yml +``` + +### Capture Django Settings + +Capture Django project settings to a YAML file: + +```bash +config-utils capture-django-settings +``` + +This will create `django_settings.yaml` with all Django settings. + +#### Options + +- `-o, --output PATH`: Specify output file path (default: `django_settings.yaml`) +- `-f, --format`: Output format, yaml or yml (default: `yaml`) +- `-s, --settings`: Django settings module (e.g., `myproject.settings`) + +#### Examples + +```bash +# Using DJANGO_SETTINGS_MODULE environment variable +export DJANGO_SETTINGS_MODULE=myproject.settings +config-utils capture-django-settings + +# Specifying settings module via command line +config-utils capture-django-settings -s myproject.settings + +# Custom output file +config-utils capture-django-settings -o my_django_config.yaml -s myproject.settings +``` + +### Using with uvx + +You can run the tool directly without installation: + +```bash +# From the project directory +uvx --from . config-utils capture-env + +# With options +uvx --from . config-utils capture-env -o custom.yaml + +# Django settings +uvx --from . config-utils capture-django-settings -s myproject.settings +``` + +## Requirements + +- Python >= 3.8 +- click >= 8.0.0 +- pyyaml >= 6.0 +- Django >= 3.2 (optional, for capture-django-settings) + +## Development + +### Setup + +```bash +# Clone or navigate to the project directory +cd config-utils + +# Install in editable mode with development dependencies +pip install -e . +``` + +### Project Structure + +``` +config-utils/ +├── config_utils/ +│ ├── __init__.py +│ └── cli.py +├── pyproject.toml +└── README.md +``` + +## License + +MIT License + +## Contributing + +Contributions are welcome! Please feel free to submit a Pull Request. diff --git a/config-utils/config_utils/__init__.py b/config-utils/config_utils/__init__.py new file mode 100644 index 0000000..0bce76b --- /dev/null +++ b/config-utils/config_utils/__init__.py @@ -0,0 +1,3 @@ +"""config-utils: CLI tool for capturing environment variables and Django settings.""" + +__version__ = "0.1.0" diff --git a/config-utils/config_utils/cli.py b/config-utils/config_utils/cli.py new file mode 100644 index 0000000..ec3afcb --- /dev/null +++ b/config-utils/config_utils/cli.py @@ -0,0 +1,137 @@ +"""CLI commands for config-utils.""" + +import os +import sys +import yaml +from pathlib import Path +import click + + +@click.group() +@click.version_option() +def main(): + """config-utils: Capture environment variables and Django settings.""" + pass + + +@main.command() +@click.option( + '--output', + '-o', + default='env_config.yaml', + help='Output file path (default: env_config.yaml)', + type=click.Path(), +) +@click.option( + '--format', + '-f', + type=click.Choice(['yaml', 'yml'], case_sensitive=False), + default='yaml', + help='Output format (default: yaml)', +) +def capture_env(output, format): + """Capture all environment variables and store them in YAML format.""" + try: + # Get all environment variables + env_vars = dict(os.environ) + + # Ensure output path is Path object + output_path = Path(output) + + # Write to YAML file + with open(output_path, 'w') as f: + yaml.dump(env_vars, f, default_flow_style=False, sort_keys=True) + + click.echo(f"✓ Captured {len(env_vars)} environment variables to {output_path}") + + except Exception as e: + click.echo(f"✗ Error: {str(e)}", err=True) + sys.exit(1) + + +@main.command() +@click.option( + '--output', + '-o', + default='django_settings.yaml', + help='Output file path (default: django_settings.yaml)', + type=click.Path(), +) +@click.option( + '--format', + '-f', + type=click.Choice(['yaml', 'yml'], case_sensitive=False), + default='yaml', + help='Output format (default: yaml)', +) +@click.option( + '--settings', + '-s', + help='Django settings module (e.g., myproject.settings)', + envvar='DJANGO_SETTINGS_MODULE', +) +def capture_django_settings(output, format, settings): + """Capture Django settings and store them in YAML format. + + Requires Django to be installed and DJANGO_SETTINGS_MODULE to be set, + or pass it via --settings option. + """ + try: + # Set Django settings module if provided + if settings: + os.environ['DJANGO_SETTINGS_MODULE'] = settings + + # Check if DJANGO_SETTINGS_MODULE is set + if 'DJANGO_SETTINGS_MODULE' not in os.environ: + click.echo( + "✗ Error: DJANGO_SETTINGS_MODULE not set. " + "Use --settings option or set the environment variable.", + err=True + ) + sys.exit(1) + + # Import Django + try: + import django + from django.conf import settings as django_settings + except ImportError: + click.echo( + "✗ Error: Django is not installed. " + "Install it with: pip install django", + err=True + ) + sys.exit(1) + + # Setup Django + django.setup() + + # Get all Django settings + settings_dict = {} + for setting in dir(django_settings): + # Skip private/magic attributes + if setting.isupper(): + try: + value = getattr(django_settings, setting) + # Convert non-serializable types to strings + if not isinstance(value, (str, int, float, bool, list, dict, type(None))): + value = str(value) + settings_dict[setting] = value + except Exception as e: + settings_dict[setting] = f"" + + # Ensure output path is Path object + output_path = Path(output) + + # Write to YAML file + with open(output_path, 'w') as f: + yaml.dump(settings_dict, f, default_flow_style=False, sort_keys=True) + + click.echo(f"✓ Captured {len(settings_dict)} Django settings to {output_path}") + + except Exception as e: + click.echo(f"✗ Error: {str(e)}", err=True) + sys.exit(1) + + +if __name__ == '__main__': + main() diff --git a/config-utils/pyproject.toml b/config-utils/pyproject.toml new file mode 100644 index 0000000..86d6b7c --- /dev/null +++ b/config-utils/pyproject.toml @@ -0,0 +1,23 @@ +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project] +name = "config-utils" +version = "0.1.0" +description = "CLI tool for capturing environment variables and Django settings" +readme = "README.md" +requires-python = ">=3.8" +dependencies = [ + "click>=8.0.0", + "pyyaml>=6.0", +] + +[project.optional-dependencies] +django = ["django>=3.2"] + +[project.scripts] +config-utils = "config_utils.cli:main" + +[tool.hatch.build.targets.wheel] +packages = ["config_utils"] From 465140180281dea66781d594a8bbc979dd381571 Mon Sep 17 00:00:00 2001 From: Ankit Khullar Date: Sat, 10 Jan 2026 21:58:45 +0530 Subject: [PATCH 2/5] Update test.yml --- .github/workflows/test.yml | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 04330c1..c4c8056 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -9,17 +9,13 @@ on: jobs: test: runs-on: ubuntu-latest - strategy: - matrix: - python-version: ['3.8', '3.9', '3.10', '3.11', '3.12'] - steps: - uses: actions/checkout@v4 - - name: Set up Python ${{ matrix.python-version }} + - name: Set up Python 3.12 uses: actions/setup-python@v5 with: - python-version: ${{ matrix.python-version }} + python-version: 3.12 - name: Install and test locust-compare working-directory: tools/locust-compare From 29951bca8de22118c53de01803ca0498b172a69c Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 10 Jan 2026 16:33:22 +0000 Subject: [PATCH 3/5] Refactor capture-django-settings to use manage.py shell - Change implementation to use subprocess with 'python manage.py shell' - Add --manage-py option to specify path to manage.py file - Remove Django as optional dependency (no longer imported directly) - Update README with new usage instructions and requirements - More Django-idiomatic approach using manage.py instead of direct imports --- config-utils/README.md | 39 +++++++----- config-utils/config_utils/cli.py | 100 ++++++++++++++++++++----------- config-utils/pyproject.toml | 3 - 3 files changed, 88 insertions(+), 54 deletions(-) diff --git a/config-utils/README.md b/config-utils/README.md index e1c76af..5da6051 100644 --- a/config-utils/README.md +++ b/config-utils/README.md @@ -31,13 +31,6 @@ Or install in editable mode for development: pip install -e . ``` -### For Django support - -Install with Django optional dependencies: - -```bash -pip install ".[django]" -``` ## Usage @@ -68,9 +61,11 @@ config-utils capture-env -o config.yml -f yml ### Capture Django Settings -Capture Django project settings to a YAML file: +Capture Django project settings to a YAML file using `python manage.py shell`: ```bash +# Run from your Django project directory +cd /path/to/your/django/project config-utils capture-django-settings ``` @@ -80,35 +75,48 @@ This will create `django_settings.yaml` with all Django settings. - `-o, --output PATH`: Specify output file path (default: `django_settings.yaml`) - `-f, --format`: Output format, yaml or yml (default: `yaml`) +- `-m, --manage-py PATH`: Path to manage.py (default: `manage.py`) - `-s, --settings`: Django settings module (e.g., `myproject.settings`) #### Examples ```bash -# Using DJANGO_SETTINGS_MODULE environment variable -export DJANGO_SETTINGS_MODULE=myproject.settings +# From Django project root directory config-utils capture-django-settings # Specifying settings module via command line config-utils capture-django-settings -s myproject.settings # Custom output file -config-utils capture-django-settings -o my_django_config.yaml -s myproject.settings +config-utils capture-django-settings -o my_django_config.yaml + +# Specify manage.py path if not in current directory +config-utils capture-django-settings -m /path/to/manage.py + +# Using DJANGO_SETTINGS_MODULE environment variable +export DJANGO_SETTINGS_MODULE=myproject.settings +config-utils capture-django-settings ``` +**Note**: This command must be run from your Django project directory or you must specify the path to `manage.py` using the `--manage-py` option. + ### Using with uvx You can run the tool directly without installation: ```bash -# From the project directory +# Capture environment variables uvx --from . config-utils capture-env # With options uvx --from . config-utils capture-env -o custom.yaml -# Django settings -uvx --from . config-utils capture-django-settings -s myproject.settings +# Django settings (from Django project directory) +cd /path/to/django/project +uvx --from /path/to/config-utils config-utils capture-django-settings + +# Or specify manage.py path +uvx --from /path/to/config-utils config-utils capture-django-settings -m /path/to/manage.py ``` ## Requirements @@ -116,7 +124,8 @@ uvx --from . config-utils capture-django-settings -s myproject.settings - Python >= 3.8 - click >= 8.0.0 - pyyaml >= 6.0 -- Django >= 3.2 (optional, for capture-django-settings) + +**For Django settings capture**: The command uses `python manage.py shell`, so Django must be installed in your Django project's environment. The config-utils tool itself does not need Django as a dependency. ## Development diff --git a/config-utils/config_utils/cli.py b/config-utils/config_utils/cli.py index ec3afcb..da075c9 100644 --- a/config-utils/config_utils/cli.py +++ b/config-utils/config_utils/cli.py @@ -3,6 +3,8 @@ import os import sys import yaml +import subprocess +import json from pathlib import Path import click @@ -64,60 +66,83 @@ def capture_env(output, format): default='yaml', help='Output format (default: yaml)', ) +@click.option( + '--manage-py', + '-m', + default='manage.py', + help='Path to manage.py (default: manage.py)', + type=click.Path(exists=True), +) @click.option( '--settings', '-s', help='Django settings module (e.g., myproject.settings)', envvar='DJANGO_SETTINGS_MODULE', ) -def capture_django_settings(output, format, settings): +def capture_django_settings(output, format, manage_py, settings): """Capture Django settings and store them in YAML format. - Requires Django to be installed and DJANGO_SETTINGS_MODULE to be set, - or pass it via --settings option. + Uses 'python manage.py shell' to access Django settings. + Requires manage.py to be present in the current directory or specify path with --manage-py. """ try: - # Set Django settings module if provided - if settings: - os.environ['DJANGO_SETTINGS_MODULE'] = settings - - # Check if DJANGO_SETTINGS_MODULE is set - if 'DJANGO_SETTINGS_MODULE' not in os.environ: + # Check if manage.py exists + manage_path = Path(manage_py) + if not manage_path.exists(): click.echo( - "✗ Error: DJANGO_SETTINGS_MODULE not set. " - "Use --settings option or set the environment variable.", + f"✗ Error: manage.py not found at {manage_path}. " + "Run this command from your Django project root or use --manage-py to specify the path.", err=True ) sys.exit(1) - # Import Django + # Python script to run in Django shell + django_script = """ +import json +from django.conf import settings + +settings_dict = {} +for setting in dir(settings): + if setting.isupper(): try: - import django - from django.conf import settings as django_settings - except ImportError: - click.echo( - "✗ Error: Django is not installed. " - "Install it with: pip install django", - err=True - ) + value = getattr(settings, setting) + # Convert non-serializable types to strings + if not isinstance(value, (str, int, float, bool, list, dict, type(None))): + value = str(value) + settings_dict[setting] = value + except Exception as e: + settings_dict[setting] = f"" + +print(json.dumps(settings_dict)) +""" + + # Prepare environment variables + env = os.environ.copy() + if settings: + env['DJANGO_SETTINGS_MODULE'] = settings + + # Run manage.py shell with the script + result = subprocess.run( + ['python', str(manage_path), 'shell'], + input=django_script, + capture_output=True, + text=True, + env=env, + timeout=30 + ) + + if result.returncode != 0: + click.echo(f"✗ Error running Django shell:", err=True) + click.echo(result.stderr, err=True) sys.exit(1) - # Setup Django - django.setup() - - # Get all Django settings - settings_dict = {} - for setting in dir(django_settings): - # Skip private/magic attributes - if setting.isupper(): - try: - value = getattr(django_settings, setting) - # Convert non-serializable types to strings - if not isinstance(value, (str, int, float, bool, list, dict, type(None))): - value = str(value) - settings_dict[setting] = value - except Exception as e: - settings_dict[setting] = f"" + # Parse JSON output + try: + settings_dict = json.loads(result.stdout.strip()) + except json.JSONDecodeError: + click.echo(f"✗ Error: Could not parse Django settings output", err=True) + click.echo(f"Output: {result.stdout}", err=True) + sys.exit(1) # Ensure output path is Path object output_path = Path(output) @@ -128,6 +153,9 @@ def capture_django_settings(output, format, settings): click.echo(f"✓ Captured {len(settings_dict)} Django settings to {output_path}") + except subprocess.TimeoutExpired: + click.echo("✗ Error: Django shell command timed out", err=True) + sys.exit(1) except Exception as e: click.echo(f"✗ Error: {str(e)}", err=True) sys.exit(1) diff --git a/config-utils/pyproject.toml b/config-utils/pyproject.toml index 86d6b7c..2645bec 100644 --- a/config-utils/pyproject.toml +++ b/config-utils/pyproject.toml @@ -13,9 +13,6 @@ dependencies = [ "pyyaml>=6.0", ] -[project.optional-dependencies] -django = ["django>=3.2"] - [project.scripts] config-utils = "config_utils.cli:main" From 0807445ff648a8f4b073a2fc770ca3129ed469a4 Mon Sep 17 00:00:00 2001 From: Ankit Khullar Date: Sun, 11 Jan 2026 12:37:25 +0530 Subject: [PATCH 4/5] fix --- config-utils/config_utils/__init__.py | 3 --- .../config-utils}/README.md | 21 ++++++++++++++++--- .../config-utils}/cli.py | 0 .../config-utils}/pyproject.toml | 4 ++-- 4 files changed, 20 insertions(+), 8 deletions(-) delete mode 100644 config-utils/config_utils/__init__.py rename {config-utils => tools/config-utils}/README.md (92%) rename {config-utils/config_utils => tools/config-utils}/cli.py (100%) rename {config-utils => tools/config-utils}/pyproject.toml (84%) diff --git a/config-utils/config_utils/__init__.py b/config-utils/config_utils/__init__.py deleted file mode 100644 index 0bce76b..0000000 --- a/config-utils/config_utils/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -"""config-utils: CLI tool for capturing environment variables and Django settings.""" - -__version__ = "0.1.0" diff --git a/config-utils/README.md b/tools/config-utils/README.md similarity index 92% rename from config-utils/README.md rename to tools/config-utils/README.md index 5da6051..954d2df 100644 --- a/config-utils/README.md +++ b/tools/config-utils/README.md @@ -9,7 +9,23 @@ A CLI tool for capturing environment variables and Django settings in YAML forma ## Installation -### Using uvx (Recommended) +### Using uv tool install (Recommended) + +Install the tool globally using `uv`: + +```bash +uv tool install . +``` + +Or install from a remote location: + +```bash +uv tool install config-utils +``` + +This will install the `config-utils` executable in your PATH. + +### Using uvx Run the tool directly without installation: @@ -143,9 +159,8 @@ pip install -e . ``` config-utils/ +├── cli.py ├── config_utils/ -│ ├── __init__.py -│ └── cli.py ├── pyproject.toml └── README.md ``` diff --git a/config-utils/config_utils/cli.py b/tools/config-utils/cli.py similarity index 100% rename from config-utils/config_utils/cli.py rename to tools/config-utils/cli.py diff --git a/config-utils/pyproject.toml b/tools/config-utils/pyproject.toml similarity index 84% rename from config-utils/pyproject.toml rename to tools/config-utils/pyproject.toml index 2645bec..10bf057 100644 --- a/config-utils/pyproject.toml +++ b/tools/config-utils/pyproject.toml @@ -14,7 +14,7 @@ dependencies = [ ] [project.scripts] -config-utils = "config_utils.cli:main" +config-utils = "cli:main" [tool.hatch.build.targets.wheel] -packages = ["config_utils"] +only-include = ["cli.py"] From a42b3b1d4e0318a063505a8a9e0fa854811c38ee Mon Sep 17 00:00:00 2001 From: Ankit Khullar Date: Sun, 11 Jan 2026 12:40:20 +0530 Subject: [PATCH 5/5] main readme --- README.md | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index cde5ce0..922397c 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ A collection of small, independent Python utilities. Each tool is self-contained | Tool | Description | |------|-------------| | [locust-compare](tools/locust-compare/) | Compare performance metrics between two Locust runs | +| [config-utils](tools/config-utils/) | CLI tool for capturing environment variables and Django settings | ## Installation @@ -16,18 +17,28 @@ Each tool can be installed independently using `uvx` directly from GitHub: For example: ```bash +# Install locust-compare uv tool install 'git+https://github.com/dev-ankit/python-tools.git#subdirectory=tools/locust-compare' + +# Install config-utils +uv tool install 'git+https://github.com/dev-ankit/python-tools.git#subdirectory=tools/config-utils' ``` -After that, you can run from anywhere: + +After installation, you can run from anywhere: +```bash locust-compare +config-utils capture-env +``` To update to the latest from GitHub: -``` +```bash uv tool upgrade locust-compare +uv tool upgrade config-utils # Or force reinstall: uv tool install --force 'git+https://github.com/dev-ankit/python-tools.git#subdirectory=tools/locust-compare' +uv tool install --force 'git+https://github.com/dev-ankit/python-tools.git#subdirectory=tools/config-utils' # To see installed tools: uv tool list @@ -42,7 +53,11 @@ uvx --from 'git+https://github.com/dev-ankit/python-tools.git#subdirectory=tools For example: ```bash +# Run locust-compare uvx --from 'git+https://github.com/dev-ankit/python-tools.git#subdirectory=tools/locust-compare' locust-compare + +# Run config-utils +uvx --from 'git+https://github.com/dev-ankit/python-tools.git#subdirectory=tools/config-utils' config-utils capture-env ``` Option 3: Clone and run locally: @@ -62,11 +77,15 @@ python-tools/ ├── .github/ │ └── workflows/ └── tools/ - └── locust-compare/ # Locust performance comparison tool - ├── compare_runs.py + ├── locust-compare/ # Locust performance comparison tool + │ ├── compare_runs.py + │ ├── pyproject.toml + │ ├── README.md + │ └── tests/ + └── config-utils/ # CLI tool for capturing environment variables and Django settings + ├── cli.py ├── pyproject.toml - ├── README.md - └── tests/ + └── README.md ``` ## Adding a New Tool