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 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 diff --git a/tools/config-utils/README.md b/tools/config-utils/README.md new file mode 100644 index 0000000..954d2df --- /dev/null +++ b/tools/config-utils/README.md @@ -0,0 +1,174 @@ +# 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 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: + +```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 . +``` + + +## 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 using `python manage.py shell`: + +```bash +# Run from your Django project directory +cd /path/to/your/django/project +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`) +- `-m, --manage-py PATH`: Path to manage.py (default: `manage.py`) +- `-s, --settings`: Django settings module (e.g., `myproject.settings`) + +#### Examples + +```bash +# 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 + +# 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 +# Capture environment variables +uvx --from . config-utils capture-env + +# With options +uvx --from . config-utils capture-env -o custom.yaml + +# 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 + +- Python >= 3.8 +- click >= 8.0.0 +- pyyaml >= 6.0 + +**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 + +### 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/ +├── cli.py +├── config_utils/ +├── pyproject.toml +└── README.md +``` + +## License + +MIT License + +## Contributing + +Contributions are welcome! Please feel free to submit a Pull Request. diff --git a/tools/config-utils/cli.py b/tools/config-utils/cli.py new file mode 100644 index 0000000..da075c9 --- /dev/null +++ b/tools/config-utils/cli.py @@ -0,0 +1,165 @@ +"""CLI commands for config-utils.""" + +import os +import sys +import yaml +import subprocess +import json +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( + '--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, manage_py, settings): + """Capture Django settings and store them in YAML format. + + 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: + # Check if manage.py exists + manage_path = Path(manage_py) + if not manage_path.exists(): + click.echo( + 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) + + # 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: + 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) + + # 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) + + # 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 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) + + +if __name__ == '__main__': + main() diff --git a/tools/config-utils/pyproject.toml b/tools/config-utils/pyproject.toml new file mode 100644 index 0000000..10bf057 --- /dev/null +++ b/tools/config-utils/pyproject.toml @@ -0,0 +1,20 @@ +[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.scripts] +config-utils = "cli:main" + +[tool.hatch.build.targets.wheel] +only-include = ["cli.py"]