diff --git a/.editorconfig b/.editorconfig index fa2d028..c392249 100644 --- a/.editorconfig +++ b/.editorconfig @@ -8,6 +8,9 @@ insert_final_newline = true trim_trailing_whitespace = true max_line_length = 120 -[{*.yml,*.yaml,*.json,*.md}] +[{*.yml,*.yaml,*.json}] indent_size = 2 max_line_length = off + +[*.md] +trim_trailing_whitespace = false diff --git a/.gitignore b/.gitignore index 292f955..3a9560d 100644 --- a/.gitignore +++ b/.gitignore @@ -151,3 +151,4 @@ mongo/ # Hatch version control from /~https://github.com/ofek/hatch-vcs pydantic_settings_export/version.py +.aider* diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..bbb6dff --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,127 @@ +# Code of Conduct for pydantic-settings-export + +## Project Context + +This is a personal project developed and maintained during personal time. +While we strive for quality and responsiveness, specific resolution timelines can't be guaranteed. + +## Communication & Contributions + +### Primary Communication Channel + +GitHub Issues are **required** as the first point of contact for: + +- Bug reports +- Feature requests +- Documentation improvements +- General questions + +### Secondary Contact Methods + +Only after creating an issue, contributors may use: + +- GitHub anonymous email: `30597878+jag-k@users.noreply.github.com` +- Other contact methods listed on the maintainer's GitHub profile + +## Development Priorities + +1. Bug fixes +2. Features from Roadmap: + - Issues with the closest milestone + - General milestone issues + - General issues with label `bug` or `feature request` + - Features listed in README.md +3. New feature proposals + +## Contribution Guidelines + +### Issue Creation + +All contributions must start with a GitHub issue to: +- Track the proposed changes +- Discuss implementation details +- Document decisions + +### Branch Naming Convention + +- Format: `/-` +- Domains: + - `fix/` for bug fixes + - `feature/` for new features +- Example: `feature/6-inject-config-to-markdown` + +### Pull Requests Requirements + +1. Must reference an existing issue +2. Must pass GitHub Actions checks +3. Must include changelog in PR description + +### CI/CD Process + +The project uses GitHub Actions for: + +- Code linting +- Testing +- Building +- Publishing to PyPI (triggered by tags) + +Version numbering: + +- GitHub tags/releases: prefixed with `v` (e.g., `v1.0.0`) +- PyPI versions: no prefix (e.g., `1.0.0`) + +## Documentation Structure + +- Primary documentation maintained in GitHub Wiki +- README.md contains essential information and roadmap +- Milestones track planned development +- PR descriptions serve as changelog entries + +## Platform Guidelines + +### GitHub Interactions + +Use [GitHub Issues][gh-issues] or [GitHub Discussions][gh-discussions] for: + +- Feature brainstorming +- Documentation questions +- Roadmap suggestions + +### PyPI Considerations + +- Package versions follow [SemVer](https://semver.org) +- Security reports should use GitHub Issues +- Package publishing is automated through GitHub Actions + +## Enforcement + +### GitHub-Specific Sanctions + +- Repository access revocation +- Issue/PR commenting restrictions +- Forking restrictions for severe cases + +### PyPI Security + +Malicious package versions will be: + +1. Reported to [PyPI Security](mailto:security@pypi.org) +2. Yanked within 24 hours of confirmation +3. Documented in GitHub Issues + +## Adaptations + +This Code of Conduct combines elements from: + +- [Contributor Covenant 2.1](https://www.contributor-covenant.org) +- [GitHub Community Guidelines](https://docs.github.com/en/site-policy/github-terms/github-community-guidelines) + +--- + +**Version**: 1.0 \ +**Effective Date**: Immediately upon merging \ +**Project Wiki**: [General docs][gh-wiki] + +[gh-issues]: /~https://github.com/jag-k/pydantic-settings-export/issues +[gh-discussions]: /~https://github.com/jag-k/pydantic-settings-export/discussions +[gh-wiki]: /~https://github.com/jag-k/pydantic-settings-export/wiki diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..3e441a7 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,100 @@ +# Contributing to pydantic-settings-export + +Thank you for your interest in contributing to pydantic-settings-export! +This document provides guidelines and instructions for contributing. + +## Development Setup + +1. **Clone the repository** + ```bash + git clone /~https://github.com/jag-k/pydantic-settings-export.git + cd pydantic-settings-export + ``` + +2. **Set up a development environment** + ```bash + # Using pip + pip install -e ".[dev,tests]" + + # Install pre-commit hooks + pre-commit install + ``` + +## Development Process + +### Code Style + +We use several tools to maintain code quality: + +- **Ruff** for linting and formatting +- **MyPy** for type checking +- **pre-commit** for automated checks + +Configuration for these tools is in `pyproject.toml`. + +### Making Changes + +1. Create a new branch: + ```bash + git checkout -b feature/your-feature-name + ``` + +2. Make your changes and ensure all tests pass: + ```bash + pytest + ``` + +3. Update documentation if needed: + ```bash + pydantic-settings-export --generator markdown + ``` + +4. Commit your changes: + ```bash + git add . + git commit -m "feat: your descriptive commit message" + ``` + + We follow the [Conventional Commits](https://www.conventionalcommits.org/) specification. + +### Pull Request Process + +1. Update the README.md if needed +2. Update documentation if you're adding/changing features +3. Add tests for new functionality +4. Ensure that all checks pass +5. Submit your PR with a clear description + +## Testing + +Run tests with pytest: +```bash +pytest +``` + +Add new tests in the `tests/` directory, following existing patterns. + +## Documentation + +- Update relevant documentation to [GitHub Wiki](/~https://github.com/jag-k/pydantic-settings-export/wiki) +- Keep docstrings up to date +- Add examples for new features + +## Release Process + +The release process is only made by maintainers: + - [@jag-k](/~https://github.com/jag-k) + +1. Create a new git tag by running `git tag -a v1.0.0 -m "Release v1.0.0"` +2. CI will automatically publish to PyPI and create a GitHub release + +## Getting Help + +- Open an [issue](/~https://github.com/jag-k/pydantic-settings-export/issues) +- Start a [discussion](/~https://github.com/jag-k/pydantic-settings-export/discussions) +- Ask questions in existing issues/discussions + +## Code of Conduct + +Please note that this project is released with a [Code of Conduct](CODE_OF_CONDUCT.md). +By participating in this project, you agree to abide by its terms. diff --git a/README.md b/README.md index 227ef32..aae58c8 100644 --- a/README.md +++ b/README.md @@ -1,98 +1,349 @@ # pydantic-settings-export -[![PyPI - Project version](https://img.shields.io/pypi/v/pydantic-settings-export?logo=pypi)](https://pypi.org/p/pydantic-settings-export/) -[![PyPI - Downloads](https://img.shields.io/pypi/dm/pydantic-settings-export)](https://pypi.org/p/pydantic-settings-export/) -[![Pepy - Total Downloads](https://img.shields.io/pepy/dt/pydantic-settings-export)](https://pypi.org/p/pydantic-settings-export/) -[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/pydantic-settings-export)](https://pypi.org/project/pydantic-settings-export/) -[![PyPI - License](https://img.shields.io/pypi/l/pydantic-settings-export)](/~https://github.com/jag-k/pydantic-settings-export/blob/main/LICENSE) +[![uv](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/uv/main/assets/badge/v0.json)](/~https://github.com/astral-sh/uv) +[![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](/~https://github.com/astral-sh/ruff) +[![PyPI - Project version](https://img.shields.io/pypi/v/pydantic-settings-export?logo=pypi)][pypi] +[![PyPI - Downloads](https://img.shields.io/pypi/dm/pydantic-settings-export)][pypi] +[![Pepy - Total Downloads](https://img.shields.io/pepy/dt/pydantic-settings-export)][pypi] +[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/pydantic-settings-export)][pypi] +[![PyPI - License](https://img.shields.io/pypi/l/pydantic-settings-export)][license] -*Export your Pydantic settings to Markdown and .env.example files!* +*Export your Pydantic settings to documentation with ease!* -This package provides a way to use [pydantic](https://docs.pydantic.dev/) (and [pydantic-settings](https://docs.pydantic.dev/latest/concepts/pydantic_settings/)) models to generate a Markdown file with the settings and their descriptions, and a `.env.example` file with the settings and their default values. +This package seamlessly integrates with [pydantic] and [pydantic-settings] to automatically generate documentation from your settings models. +Create Markdown docs, `.env.example` files, and more with minimal configuration. + +## ✨ Key Features + +- 📝 Documentation Generation + - Markdown with tables and descriptions + - Environment files (`.env.example`) + - Support for region injection in existing files + - Customizable output formats + +- 🔄 Smart Configuration Handling + - Automatic type detection + - Environment variables validation + - Default values preservation + - Optional/required fields distinction + +- 🛠 Flexible Integration + - Command-line interface + - [pre-commit] hook support + - GitHub Actions compatibility + - Python API for custom solutions + +- 🔌 Additional Features + - Email validation support (optional) + - Markdown region injection (optional) + - Multiple output paths for each generator + - Configurable through `pyproject.toml` + +## 📋 Requirements + +- Python 3.11 or higher +- pydantic >= 2.7 +- pydantic-settings >= 2.3 + +Optional dependencies: + +- `email-validator >= 2.2.0` (for email validation) +- `text-region-parser >= 0.1.1` (for Markdown region insertion) + +Install with optional dependencies: + +```bash +# Install with all optional dependencies +pip install "pydantic-settings-export[email,regions]" + +# Install with specific optional dependency +pip install "pydantic-settings-export[email]" +pip install "pydantic-settings-export[regions]" +``` + +## 🚀 Quick Start + +1. Install the package: + ```bash + pip install pydantic-settings-export + ``` +2. Create your settings model: + ```python + from pydantic_settings import BaseSettings + + class AppSettings(BaseSettings): + """Application settings.""" + debug: bool = False + api_key: str + ``` +3. Generate documentation: + ```bash + pydantic-settings-export app.settings:AppSettings + ``` + +For more detailed usage, see our [Getting Started Guide][gh-wiki/getting-started]. + +> Note: The package follows [SemVer](https://semver.org). +> GitHub releases/tags use `v` prefix (e.g. `v1.0.0`), while PyPI versions don't (e.g. `1.0.0`). ## Installation +Choose your preferred installation method: + ```bash +# Using pip pip install pydantic-settings-export -# or -pipx install pydantic-settings-export # for a global installation and using as a CLI -# or + +# Using pipx (recommended for CLI usage) +pipx install pydantic-settings-export + +# Using uv uv tool install pydantic-settings-export ``` ## Usage -You can see the usage examples of this package in the [./docs/Configuration.md](/~https://github.com/jag-k/pydantic-settings-export/blob/main/docs/Configuration.md) and [.env.example](/~https://github.com/jag-k/pydantic-settings-export/blob/main/.env.example). +The recommended way to use this package is through its CLI or as a [pre-commit] hook. -### As code +### CLI Usage -This project is not well-designed for using as a library. But you still can use it as a code. +The CLI provides a powerful interface for generating documentation: -```python -from pydantic import BaseSettings - -from pydantic_settings_export import Exporter, PSESettings -from pydantic_settings_export.generators import MarkdownGenerator - - -class Settings(BaseSettings): - my_setting: str = "default value" - another_setting: int = 42 - - -# Export the settings to a Markdown file `docs/Configuration.md` -pse_settings = PSESettings() -Exporter( - pse_settings, - generators=[ - MarkdownGenerator( - pse_settings, - MarkdownGenerator.config( - save_dirs=["docs"], - ), - ) - ] -).run_all(Settings) -``` +```bash +# Basic usage +pydantic-settings-export your_app.settings:Settings -### As CLI +# Multiple generators +pydantic-settings-export --generator markdown --generator dotenv your_app.settings:Settings - -```bash +# Help with all options and sub-commands pydantic-settings-export --help ``` - + +For complete documentation including: + +- All command options +- Environment variables +- Pre-commit integration +- Troubleshooting guide + +See the [CLI Documentation][gh-wiki/cli] + +### pre-commit hook + +The tool can be used as a pre-commit hook to automatically update documentation: + + +```yaml +# .pre-commit-config.yaml +repos: + - repo: /~https://github.com/jag-k/pydantic-settings-export + # Use a tag version with the `v` prefix (e.g. v1.0.0) + rev: v1.0.0 + hooks: + - id: pydantic-settings-export + # Optionally, specify the settings file to trigger the hook only on changes to this file + files: ^app/config/settings\.py$ + # Optionally, add extra dependencies + additional_dependencies: + - pydantic-settings-export[email,regions] +``` + +NOTE: You can use `pre-commit autoupdate` to update the hook to the latest version. + +### CI/CD Integration + + +```yaml +# .github/workflows/docs.yml +name: Update Settings Documentation +on: + push: + # Optionally, specify the settings file to trigger the hook only on changes to this file + paths: [ '**/settings.py' ] +jobs: + docs: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.11" # Minimum required version + - run: pip install pydantic-settings-export + - run: pydantic-settings-export your_app.settings:Settings +``` + +### Programmatic Usage + +While CLI is the recommended way, you can also use the package programmatically: + +```python +from pydantic_settings import BaseSettings +from pydantic_settings_export import Exporter + + +class MySettings(BaseSettings): + """Application settings.""" + debug: bool = False + api_key: str + + class Config: + env_prefix = "APP_" + + +# Create and run exporter +exporter = Exporter() +exporter.run_all(MySettings) +``` + +This will generate documentation using all available generators. For custom configuration, see our [Wiki][gh-wiki]. ## Configuration -You can add a `pydantic_settings_export` section to your `pyproject.toml` file to configure the exporter. +Basic configuration in `pyproject.toml`: ```toml - [tool.pydantic_settings_export] project_dir = "." -default_settings = [ - "pydantic_settings_export.settings:PSESettings", -] +default_settings = ["my_app.settings:AppSettings"] +env_file = ".env" + +# Generate Markdown docs +[[tool.pydantic_settings_export.generators.markdown]] +paths = ["docs/settings.md"] +# Generate .env example [[tool.pydantic_settings_export.generators.dotenv]] -paths = [ - ".env.example", -] - -[tool.pydantic_settings_export.generators.markdown] -paths = [ - "docs/Configuration.md", - "wiki/Configuration.md", -] +paths = [".env.example"] ``` -## Todo +For advanced configuration options, see our [Configuration Guide][gh-wiki/config]. + +## Examples + +See real-world examples of different output formats: + +### Environment Files + +- [.env.example](examples/.env.example) - Full example with comments and sections +- [.env.only-optional.example](examples/.env.only-optional.example) - Example with only optional fields + +### Documentation + +- [Configuration.md](examples/Configuration.md) - Full configuration documentation with tables and descriptions +- [SimpleConfiguration.md](examples/SimpleConfiguration.md) - Basic table-only configuration +- [InjectedConfiguration.md](examples/InjectedConfiguration.md) - Configuration injected into existing file + +## 📚 Learn More + +Check out our comprehensive documentation: + +- 🏁 [Getting Started Guide][gh-wiki/getting-started] +- ⚙️ [Configuration Options][gh-wiki/config] +- 🔍 [Understanding Parsers][gh-wiki/parsers] +- 🎨 [Available Generators][gh-wiki/generators] +- 💻 [CLI Documentation][gh-wiki/cli] + +## 🎯 Why This Project? + +Managing configuration in Python applications can be challenging: + +- Documentation gets outdated +- Environment variables are poorly documented +- Configuration options are scattered + +This project solves these problems by: + +- Automatically generating documentation from your Pydantic models +- Keeping documentation in sync with code +- Providing multiple output formats for different use cases + +## Development Context + +This is a personal pet project maintained in my spare time. The development priorities are: + +1. Bug fixes +2. Features from Roadmap: + - Issues with closest milestone. + - General milestone issues. + - Issues labeled `bug` or `feature request`. + - Features listed in this README. +3. New feature proposals + +> Note: While we strive for quality and responsiveness, resolution timelines can't be guaranteed. + +### Development Tools + +This project uses modern Python development tools: + +- 🚀 [uv] - Fast Python package installer and resolver +- 🔍 [ruff] - Fast Python linter and formatter +- 📦 [hatch] - Modern Python project management +- ✅ [pre-commit] - Git hooks management + +## Contributing -- [x] Add more configuration options -- [ ] Add tests +We welcome contributions! Before contributing: +1. Create a GitHub Issue first — this is **required** +2. Fork the repository +3. Create a branch following our naming convention: + - Format: `/-`. + - Domains: `fix` or `feature`. + - Example: `feature/6-inject-config-to-markdown`. +4. Make your changes +5. Submit a PR with changelog in description + +For complete guidelines, see our: + +- [Code of Conduct](CODE_OF_CONDUCT.md) +- [Contributing Guide](CONTRIBUTING.md) + +## Support + +### Primary Contact + +- 🐛 [Issue Tracker][gh-issues] (required first point of contact) + +### Secondary Contact (after creating an issue) + +- 📧 GitHub email: `30597878+jag-k@users.noreply.github.com` +- 💬 [Discussions][gh-discussions] +- 📚 [Documentation][gh-wiki] ## License -[MIT](/~https://github.com/jag-k/pydantic-settings-export/blob/main/LICENCE) +[MIT][license] + + +[pypi]: https://pypi.org/project/pydantic-settings-export/ + +[license]: /~https://github.com/jag-k/pydantic-settings-export/blob/main/LICENSE + +[gh-wiki]: /~https://github.com/jag-k/pydantic-settings-export/wiki + +[gh-wiki/cli]: /~https://github.com/jag-k/pydantic-settings-export/wiki/CLI + +[gh-wiki/config]: /~https://github.com/jag-k/pydantic-settings-export/wiki/Configuration + +[gh-wiki/getting-started]: /~https://github.com/jag-k/pydantic-settings-export/wiki/Getting-Started + +[gh-wiki/parsers]: /~https://github.com/jag-k/pydantic-settings-export/wiki/Parsers + +[gh-wiki/generators]: /~https://github.com/jag-k/pydantic-settings-export/wiki/Generators + +[gh-issues]: /~https://github.com/jag-k/pydantic-settings-export/issues + +[gh-discussions]: /~https://github.com/jag-k/pydantic-settings-export/discussions + +[pydantic]: /~https://github.com/pydantic/pydantic + +[pydantic-settings]: /~https://github.com/pydantic/pydantic-settings + +[pre-commit]: /~https://github.com/pre-commit/pre-commit + +[uv]: /~https://github.com/astral-sh/uv + +[ruff]: /~https://github.com/astral-sh/ruff + +[hatch]: /~https://github.com/pypa/hatch diff --git a/docs/.env.example b/docs/.env.example deleted file mode 100644 index 5ec1c87..0000000 --- a/docs/.env.example +++ /dev/null @@ -1,12 +0,0 @@ -### Global Settings - -# PYDANTIC_SETTINGS_EXPORT__DEFAULT_SETTINGS=[] # ["settings:settings"], ["app.config.settings:Settings","app.config.settings.dev:Settings"] -# PYDANTIC_SETTINGS_EXPORT__ROOT_DIR="" -# PYDANTIC_SETTINGS_EXPORT__PROJECT_DIR="" -# PYDANTIC_SETTINGS_EXPORT__RESPECT_EXCLUDE=true -# PYDANTIC_SETTINGS_EXPORT__ENV_FILE=null - -### Relative Directory Settings - -# PYDANTIC_SETTINGS_EXPORT__RELATIVE_TO__REPLACE_ABS_PATHS=true -# PYDANTIC_SETTINGS_EXPORT__RELATIVE_TO__ALIAS="" diff --git a/docs/.env.only-optional.example b/docs/.env.only-optional.example deleted file mode 100644 index a03905b..0000000 --- a/docs/.env.only-optional.example +++ /dev/null @@ -1,7 +0,0 @@ -# PYDANTIC_SETTINGS_EXPORT__DEFAULT_SETTINGS=[] -# PYDANTIC_SETTINGS_EXPORT__ROOT_DIR="" -# PYDANTIC_SETTINGS_EXPORT__PROJECT_DIR="" -# PYDANTIC_SETTINGS_EXPORT__RESPECT_EXCLUDE=true -# PYDANTIC_SETTINGS_EXPORT__ENV_FILE=null -# PYDANTIC_SETTINGS_EXPORT__RELATIVE_TO__REPLACE_ABS_PATHS=true -# PYDANTIC_SETTINGS_EXPORT__RELATIVE_TO__ALIAS="" diff --git a/.env.example b/examples/.env.example similarity index 100% rename from .env.example rename to examples/.env.example diff --git a/.env.only-optional.example b/examples/.env.only-optional.example similarity index 100% rename from .env.only-optional.example rename to examples/.env.only-optional.example diff --git a/docs/Configuration.md b/examples/Configuration.md similarity index 100% rename from docs/Configuration.md rename to examples/Configuration.md diff --git a/docs/InjectedConfiguration.md b/examples/InjectedConfiguration.md similarity index 100% rename from docs/InjectedConfiguration.md rename to examples/InjectedConfiguration.md diff --git a/docs/SimpleConfiguration.md b/examples/SimpleConfiguration.md similarity index 100% rename from docs/SimpleConfiguration.md rename to examples/SimpleConfiguration.md diff --git a/pydantic_settings_export/cli.py b/pydantic_settings_export/cli.py index 43eb78c..8764f81 100644 --- a/pydantic_settings_export/cli.py +++ b/pydantic_settings_export/cli.py @@ -167,19 +167,23 @@ def file_type(path: str) -> Path: def make_parser() -> argparse.ArgumentParser: - """ - Creates and configures an argparse.ArgumentParser instance for the CLI tool. - - The parser is designed to handle several command-line arguments for exporting - pydantic settings to a file, as well as manage configuration options. It includes - customized help, versioning, and configuration settings for enhanced functionality. - - :return: An instance of argparse.ArgumentParser fully configured for the CLI tool. - :rtype: argparse.ArgumentParser - - :raises ValueError: Raised by dir_type or file_type if invalid values are provided - for the `--project-dir` or `--config-file` options. - :raises FileNotFoundError: Raised if a specified file (e.g., `.env` file) does not exist. + """Create and configure the CLI argument parser. + + This function sets up a comprehensive CLI interface that supports: + - Multiple output formats (markdown, dotenv, etc) + - Custom generator plugins + - Environment variable loading + - Project-specific configuration + + Example usage: + pydantic-settings-export app.settings:Settings + pydantic-settings-export --generator markdown --output docs/settings.md app.settings:Settings + pydantic-settings-export --env-file .env.dev app.settings:Settings + + :return: Configured parser instance. + :raises ValueError: If invalid directory/file paths are provided. + :raises FileNotFoundError: If specified files don't exist. + :raises ImportError: If custom generators/settings can't be imported. """ parser = argparse.ArgumentParser( prog=PROJECT_NAME, diff --git a/pydantic_settings_export/exporter.py b/pydantic_settings_export/exporter.py index 915bfca..50890ea 100644 --- a/pydantic_settings_export/exporter.py +++ b/pydantic_settings_export/exporter.py @@ -1,3 +1,4 @@ +import warnings from pathlib import Path from pydantic_settings import BaseSettings @@ -18,6 +19,13 @@ def __init__( generators: list[AbstractGenerator] | None = None, ) -> None: self.settings: PSESettings = settings or PSESettings() + if generators is None: + generators = [] + for generator_class in AbstractGenerator.ALL_GENERATORS: + try: + generators.append(generator_class(self.settings)) + except Exception as e: + warnings.warn(f"Failed to initialize generator {generator_class.__name__}: {e}", stacklevel=2) self.generators: list[AbstractGenerator] = generators def run_all(self, *settings: BaseSettings | type[BaseSettings]) -> list[Path]: diff --git a/pydantic_settings_export/generators/dotenv.py b/pydantic_settings_export/generators/dotenv.py index dc7a368..7c6642b 100644 --- a/pydantic_settings_export/generators/dotenv.py +++ b/pydantic_settings_export/generators/dotenv.py @@ -11,6 +11,13 @@ __all__ = ("DotEnvGenerator",) DotEnvMode = Literal["all", "only-optional", "only-required"] +# Map from DotEnvMode to (is_optional, is_required) +DOTENV_MODE_MAP: dict[DotEnvMode, tuple[bool, bool]] = { + "all": (True, True), + "only-optional": (True, False), + "only-required": (False, True), +} +DOTENV_MODE_MAP_DEFAULT = DOTENV_MODE_MAP["all"] class DotEnvSettings(BaseGeneratorSettings): @@ -67,16 +74,18 @@ class DotEnvGenerator(AbstractGenerator): def generate_single(self, settings_info: SettingsInfoModel, level=1) -> str: """Generate a .env example for a pydantic settings class. - :param level: The level of nesting. Used for indentation. - :param settings_info: The settings class to generate a .env example for. - :return: The generated .env example. + Creates a formatted .env file with: + - Optional/required variables clearly marked + - Grouped settings with headers + - Example values as comments + - Proper environment variable naming + + :param settings_info: The settings class to generate examples for. + :param level: The level of nesting for proper formatting. + :return: Formatted .env content with variables and documentation. """ result = "" - is_optional, is_required = { - "all": (True, True), - "only-optional": (True, False), - "only-required": (False, True), - }.get(self.generator_config.mode, (True, True)) + is_optional, is_required = DOTENV_MODE_MAP.get(self.generator_config.mode, DOTENV_MODE_MAP_DEFAULT) if self.generator_config.split_by_group: result = f"### {settings_info.name}\n\n" diff --git a/pydantic_settings_export/generators/markdown.py b/pydantic_settings_export/generators/markdown.py index 52b9806..b9b7514 100644 --- a/pydantic_settings_export/generators/markdown.py +++ b/pydantic_settings_export/generators/markdown.py @@ -2,7 +2,7 @@ import warnings from enum import StrEnum from pathlib import Path -from typing import Literal, Self, TypedDict, cast +from typing import TYPE_CHECKING, Literal, Self, TypedDict, cast from pydantic import ConfigDict, Field, model_validator @@ -11,6 +11,9 @@ from .abstract import AbstractGenerator, BaseGeneratorSettings +if TYPE_CHECKING: + from text_region_parser import RegionConstructor + __all__ = ("MarkdownGenerator",) @@ -172,12 +175,49 @@ class MarkdownGenerator(AbstractGenerator): def _make_table(self, rows: list[TableRowDict]) -> str: return make_pretty_md_table_from_dict(rows, headers=[h.value for h in self.generator_config.table_headers]) + def _process_region(self, path: Path, content: str, constructor: "RegionConstructor") -> bool: + """Process a single region in a file. + + :param path: Path to the file + :param content: Content to insert + :param constructor: Region constructor instance + :return: True if a file was updated, False otherwise + :raises FileNotFoundError: If the file doesn't exist + :raises IOError: If there are issues reading/writing the file + """ + try: + if not path.is_file(): + raise FileNotFoundError( + f"The file {path} does not exist. " + f"Please create this file before running the generator with the `region` option." + ) + + file_content = path.read_text() + new_content = constructor.parse_content(file_content) + + if new_content == file_content: + return False + + path.write_text(new_content) + return True + + except OSError as e: + raise OSError(f"Failed to process region in {path}: {e}") from e + def generate_single(self, settings_info: SettingsInfoModel, level: int = 1) -> str: # noqa: C901 """Generate Markdown documentation for a pydantic settings class. - :param settings_info: The settings class to generate documentation for. - :param level: The level of nesting. Used for indentation. - :return: The generated documentation. + Creates formatted Markdown with: + - Nested headers for settings hierarchy + - Tables for settings documentation + - Environment variable information + - Type annotations and defaults + - Optional examples and deprecation notices + + + :param settings_info: Settings model to document. + :param level: Header nesting level (h1, h2, etc). + :return: Formatted Markdown documentation. """ # Generate header result = "" @@ -250,18 +290,10 @@ def run(self, *settings_info: SettingsInfoModel) -> list[Path]: updated_files: list[Path] = [] for path in file_paths: - if not path.is_file(): - raise FileNotFoundError( - f"The file {path} does not exist. " - f"Please, create this file before running the generator with the `region` option." - ) - - file_content = path.read_text() - new_content = constructor.parse_content(file_content) - if new_content == file_content: - # No need to update the file - continue - path.write_text(new_content) - updated_files.append(path) + try: + if self._process_region(path, result, constructor): + updated_files.append(path) + except (OSError, FileNotFoundError) as e: + warnings.warn(str(e), stacklevel=2) return updated_files diff --git a/pydantic_settings_export/generators/simple.py b/pydantic_settings_export/generators/simple.py index a0d1508..48d3159 100644 --- a/pydantic_settings_export/generators/simple.py +++ b/pydantic_settings_export/generators/simple.py @@ -6,6 +6,9 @@ __all__ = ("SimpleGenerator",) +INDENT_CHAR = " " +HEADER_UNDERLINE_CHAR = "=" + class SimpleSettings(BaseGeneratorSettings): """Settings for the simple generator.""" @@ -21,20 +24,33 @@ class SimpleGenerator(AbstractGenerator): generator_config: SimpleSettings def generate_single(self, settings_info: SettingsInfoModel, level: int = 1) -> str: # noqa: C901 - """Generate Markdown documentation for a pydantic settings class. + """Generate simple text documentation for settings. + + Produces a clean, readable text format with: + - Section headers with underlines + - Field descriptions and types + - Default values and examples + - Proper spacing and indentation - :param settings_info: The settings class to generate documentation for. - :param level: The level of nesting. Used for indentation. - :return: The generated documentation. + :param settings_info: Settings model to document. + :param level: Nesting level for indentation. + :return: Formatted text documentation with consistent styling. """ + indent = INDENT_CHAR * (level - 1) docs = settings_info.docs.rstrip() - # Generate header + # Generate section header name = settings_info.name - hash_len = len(name) - result = f"{name}\n{'#' * hash_len}\n" + header_line = HEADER_UNDERLINE_CHAR * len(name) + result = f"{indent}{name}\n{indent}{header_line}\n" + + # Add environment prefix if present + if settings_info.env_prefix: + result += f"\n{indent}Environment Prefix: {settings_info.env_prefix}\n" + + # Add documentation if present if docs: - result += f"\n{docs}\n" + result += f"\n{indent}{docs}\n" for field in settings_info.fields: field_name = f"`{field.full_name}`" diff --git a/pyproject.toml b/pyproject.toml index b7d00f3..fe8319f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "pydantic-settings-export" dynamic = ["version"] -description = "Export your Pydantic settings to a Markdown and .env.example files!" +description = "Export your Pydantic settings to documentation with ease!" readme = "README.md" authors = [ { name = "Jag_k", email = "30597878+jag-k@users.noreply.github.com" } @@ -178,14 +178,12 @@ env_file = ".env" [[tool.pydantic_settings_export.generators.dotenv]] paths = [ - ".env.example", - "docs/.env.example", + "examples/.env.example", ] [[tool.pydantic_settings_export.generators.dotenv]] paths = [ - ".env.only-optional.example", - "docs/.env.only-optional.example", + "examples/.env.only-optional.example", ] mode = "only-optional" split_by_group = false @@ -193,7 +191,7 @@ add_examples = false [[tool.pydantic_settings_export.generators.markdown]] paths = [ - "docs/Configuration.md", + "examples/Configuration.md", "wiki/Configuration.md", ] @@ -201,7 +199,7 @@ paths = [ table_only = true file_prefix = "Simple Configuration. Just a table." paths = [ - "docs/SimpleConfiguration.md", + "examples/SimpleConfiguration.md", ] [[tool.pydantic_settings_export.generators.markdown]] @@ -209,5 +207,5 @@ table_only = true file_prefix = "Injected Configuration. Just a table." region = "config" paths = [ - "docs/InjectedConfiguration.md", + "examples/InjectedConfiguration.md", ]