Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor generators and improve configuration handling #23

Merged
merged 3 commits into from
Feb 18, 2025

Conversation

jag-k
Copy link
Owner

@jag-k jag-k commented Feb 18, 2025

Closes #6

What have been done:

  • Split generators into separate modules for better organization
  • Add support for multiple generator configurations
  • Add new table_only option for markdown generator
  • Add simple generator for basic table output
  • Improve type hints and documentation
  • Deprecate old-style generator configuration
  • Add support for deprecated field marking
  • Add new examples to documentation

Summary by CodeRabbit

  • New Features
    • Extended dependency support to enable enhanced email and region functionality.
    • Enabled region-aware processing for generating tailored markdown documentation.
    • Expanded generator settings to provide improved documentation output.
  • Documentation
    • Added an example section illustrating how injected configuration works using a table format.
    • Updated documentation for clarity regarding the env_file field in CLI settings.
    • Reorganized table structure in configuration documentation for improved readability.

    - Introduce `region` field to markdown generator configuration for injecting specific regions in documentation.
    - Validate `region` functionality and enforce `regions` optional dependency if used.
    - Update `.pre-commit-config.yaml` and `pyproject.toml` with `email` and `regions` extra dependencies.
    - Add `InjectedConfiguration.md` as an example of region-based documentation generation.
    - Include new dependencies (`email-validator`, `text-region-parser`) in `uv.lock`.
@jag-k jag-k self-assigned this Feb 18, 2025
@jag-k jag-k changed the base branch from main to dev February 18, 2025 17:44
Repository owner deleted a comment from coderabbitai bot Feb 18, 2025
@jag-k jag-k added the feature request New feature or request label Feb 18, 2025
@jag-k jag-k added this to the v0.4.0 milestone Feb 18, 2025
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Inline review comments failed to post. This is likely due to GitHub's limits when posting large numbers of comments.

Actionable comments posted: 1

🔭 Outside diff range comments (1)
pydantic_settings_export/settings.py (1)

23-31: 💡 Verification agent

🧩 Analysis chain

Verify the impact of removed generator configuration.

The removal of generator-related fields from PSESettings aligns with the refactoring goals. However, ensure that all existing usages of the removed functionality have been updated accordingly.


🏁 Script executed:

#!/bin/bash
# Search for any remaining references to the removed generator configuration
rg -A 5 "generators\s*=|generators_list\s*=|env_file\s*=" --type py

Length of output: 1872


Verify Generator Configuration Impact Across the Codebase

The refactoring removing generator-related fields in PSESettings aligns with the intended modular design. However, note the following concerns:

  • In pydantic_settings_export/cli.py, there are still assignments using generator configuration (e.g., s.generators_list = generators). Ensure that these references are updated to reflect the new design or handled appropriately if they now rely on a different mechanism.
  • The utilities in pydantic_settings_export/utils.py invoke AbstractGenerator.generators(), which appears independent of PSESettings changes. However, verify that any interaction with generator settings in CLI does not lead to runtime errors due to missing fields.

Please confirm that all usages of the now-removed generator-related functionality have been refactored accordingly to avoid inconsistencies or failures.

🧹 Nitpick comments (10)
pydantic_settings_export/utils.py (1)

19-45: Consider handling row/column length mismatches.
When iterating over rows, there's no explicit check that each row matches the length of headers. If a row is shorter or longer, an index error could occur. For safety, you could add a length check before processing each row.

 def make_pretty_md_table(headers: list[str], rows: list[list[str]]) -> str:  # noqa: C901
     ...
+    # Validate that each row has the same number of cells as headers
+    for row_index, row in enumerate(rows):
+        if len(row) != len(headers):
+            raise ValueError(
+                f"Row {row_index} length ({len(row)}) does not match headers length ({len(headers)})."
+            )

     col_sizes = [len(h) for h in headers]
     ...
pydantic_settings_export/generators/abstract.py (2)

49-55: Add a docstring for the generator_config parameter.

The constructor gains a new parameter generator_config, but the docstring does not mention it. Documenting the purpose and usage of this parameter would improve readability.


66-66: Guard against None docstrings.

Appending a string to cls.config.__doc__ could raise an error if cls.config.__doc__ is None. Consider a fallback assignment, for example:

cls.config.__doc__ = (cls.config.__doc__ or "") + f"\n\nGenerator name: `{cls.name}`."
- cls.config.__doc__ += f"\n\nGenerator name: `{cls.name}`."
+ cls.config.__doc__ = (cls.config.__doc__ or "") + f"\n\nGenerator name: `{cls.name}`."
pydantic_settings_export/cli.py (1)

111-121: Gathering generator instances from config.

Iterating over each named generator config to instantiate them is an excellent pattern. It cleanly finishes the pipeline from config -> generator objects.

Consider logging a warning if a generator name does not exist in all_generators for better debugging.

pydantic_settings_export/models.py (3)

1-2: Consolidate import statements if necessary.
Imports for json, typing, and pydantic have been added. Ensure all imports are actually needed. If certain imports are unused, consider removing them to keep the file clean.

Also applies to: 8-8


126-129: Revisit example-check logic.
has_examples checks self.examples != [self.default]. If default is a string or if there are multiple examples, this might skip certain corner cases (e.g., multiple examples, some matching the default). Consider whether you need stricter or more flexible logic here.


188-198: Avoid duplicate aliases.
When gathering aliases from field.alias, field.validation_alias, AliasChoices, or AliasPath, consider deduplicating them. Repeated entries can lead to confusion or inconsistent usage down the line.

pydantic_settings_export/exporter.py (1)

5-6: Refine generator handling approach.
Switching from a list of generator types to a list of generator instances is cleaner. If you still want a default set of generators when none is provided, consider adding a fallback instead of storing None. Also confirm that removing self.settings from the generator’s run call does not break existing usage patterns.

Also applies to: 18-21, 37-37

pydantic_settings_export/generators/dotenv.py (1)

75-79: Consider extracting mode mapping to a constant.

The mode mapping dictionary could be moved to a class-level constant for better readability and reusability.

+MODE_SETTINGS = {
+    "all": (True, True),
+    "only-optional": (True, False),
+    "only-required": (False, True),
+}

 def generate_single(self, settings_info: SettingsInfoModel, level=1) -> str:
     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 = self.MODE_SETTINGS.get(self.generator_config.mode, (True, True))
README.md (1)

29-29: Improve Language Clarity
The sentence “This project is not well-designed for using as a library. But you still can use it as a code.” can be rephrased for better clarity and flow. Consider a revision such as: “This project is not primarily designed as a library, but it can still be used as standalone code.”

🧰 Tools
🪛 LanguageTool

[uncategorized] ~29-~29: These words/punctuation marks might seem a little out of order. For clarity and coherence, try switching them around.
Context: ...esigned for using as a library. But you still can use it as a code. ```python from pydan...

(AI_EN_LECTOR_REPLACEMENT_WORD_ORDER)

🛑 Comments failed to post (1)
pydantic_settings_export/models.py (1)

50-80: ⚠️ Potential issue

Ensure consistent list return for ForwardRef annotations.
In get_type_by_annotation, when encountering a typing.ForwardRef, the code returns a string instead of a list. This contradicts the function signature -> list[str]. Consider wrapping the return in a list for consistency, e.g.:

-if isinstance(annotation, typing.ForwardRef):
-    return annotation.__forward_value__ or annotation.__forward_arg__
+if isinstance(annotation, typing.ForwardRef):
+    return [annotation.__forward_value__ or annotation.__forward_arg__]
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

def get_type_by_annotation(annotation: Any, remove_none: bool = True) -> list[str]:
    args = getattr(annotation, "__args__", [])
    if remove_none:
        args = [arg for arg in args if arg is not None]

    # If it is an Alias (like `list[int]`), get the origin (like `list`)
    if isinstance(annotation, typing.GenericAlias):
        annotation = annotation.__origin__

    # If it is a Union, get all types to return something like `integer | string`
    # instead of `Union[int, str]`, `int | str`, or `Union`.
    if isinstance(annotation, UnionType | typing._UnionGenericAlias):
        args = list(filter(bool, args))
        if args:
            return [t for arg in args for t in get_type_by_annotation(arg)]
        else:
            annotation = None

    # If it is a Literal, get all types to return in "original" format like `1 | 'some-str'`
    # instead of `Literal[1, 'some-str']` or `Literal`.
    if isinstance(annotation, typing._LiteralGenericAlias):
        return [json.dumps(a, default=repr) for a in args]

    # If it is a ForwardRef, get the value or the argument to return something like `CustomType`
    # instead of `"CustomType"`, `ForwardRef("CustomType")` or `ForwardRef`.
    if isinstance(annotation, typing.ForwardRef):
        return [annotation.__forward_value__ or annotation.__forward_arg__]

    # Map the annotation to the type in the FIELD_TYPE_MAP
    return [FIELD_TYPE_MAP.get(annotation, annotation.__name__ if annotation else "any")]

Copy link

coderabbitai bot commented Feb 18, 2025

📝 Walkthrough

Walkthrough

The pull request introduces updates to configuration settings, documentation, and code to enhance region-specific markdown processing. The pre-commit hook configuration now specifies additional dependencies, while optional dependency groups for email and regions are added in the project configuration. Documentation is improved with an example illustrating how injected configuration regions function. In the codebase, a new region field and corresponding validator are added to the markdown settings, along with a new run method in the markdown generator for conditional region-specific processing.

Changes

File(s) Change Summary
.pre-commit-config.yaml, pyproject.toml Updated dependency configurations: the pre-commit hook now includes additional_dependencies: - '.[email,regions]' and pyproject.toml defines new optional dependency groups (email and regions) along with a new markdown generator entry.
docs/InjectedConfiguration.md Added an example section demonstrating how injected configuration works using region markers (e.g., <!-- region:config --> and <!-- endregion:config -->).
pydantic_settings_export/generators/markdown.py Introduced a new region field and a validate_region method in MarkdownSettings, and added a run method in MarkdownGenerator for region-specific markdown processing with conditional handling and file updates.
pydantic_settings_export/cli.py Updated documentation for the env_file field in PSECLISettings class for clarity.
docs/Configuration.md, docs/SimpleConfiguration.md Reorganized and reformatted tables in documentation without altering content or functionality.

Sequence Diagram(s)

sequenceDiagram
    participant Caller as Generator Requester
    participant MGen as MarkdownGenerator
    participant Parser as text_region_parser
    participant FS as File System

    Caller->>MGen: run(settings_info)
    alt region not set
       MGen->>MGen: Call default run behavior (superclass)
    else region set
       MGen->>Parser: Import and generate region-specific content
       Parser-->>MGen: Return parsed region content
       MGen->>FS: Check file existence and update content if needed
    end
Loading

📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ed89e66 and d9317c2.

📒 Files selected for processing (4)
  • docs/Configuration.md (1 hunks)
  • docs/InjectedConfiguration.md (1 hunks)
  • docs/SimpleConfiguration.md (1 hunks)
  • pydantic_settings_export/cli.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • docs/SimpleConfiguration.md
  • docs/Configuration.md
  • pydantic_settings_export/cli.py
  • docs/InjectedConfiguration.md

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@jag-k
Copy link
Owner Author

jag-k commented Feb 18, 2025

@coderabbitai review

Copy link

coderabbitai bot commented Feb 18, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (3)
pydantic_settings_export/generators/markdown.py (2)

88-96: Clarify documentation for region
The docstring references "If True, ..." even though True is not a valid literal for region. Consider updating the description or the type hint to avoid confusion.

-            "If `True`, will be generated with the header of top-level settings.\n\n"
+            "If a string is provided, the generator will insert content into that named region.\n\n"

227-265: Region-based content replacement
If the same region placeholder appears multiple times, all instances will be replaced with the same content. This may be helpful or unexpected. Documenting this behavior could prevent confusion for end-users.

docs/InjectedConfiguration.md (1)

16-16: Refine explanatory text
The phrase “Useful, then you have a Settings class/instance...” is slightly unclear. A small grammar fix would improve clarity.

-| `PYDANTIC_SETTINGS_EXPORT__ENV_FILE`                       | `Path` \| `NoneType` | `null`            | The path to the `.env` file to load environment variables. Useful, then you have a Settings class/instance, which require values while running. | `null`                                                                                         |
+| `PYDANTIC_SETTINGS_EXPORT__ENV_FILE`                       | `Path` \| `NoneType` | `null`            | The path to the `.env` file to load environment variables. Useful when you have a Settings class/instance that requires values while running. | `null`                                                                                         |
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0a7b256 and 04242ce.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (4)
  • .pre-commit-config.yaml (1 hunks)
  • docs/InjectedConfiguration.md (1 hunks)
  • pydantic_settings_export/generators/markdown.py (4 hunks)
  • pyproject.toml (2 hunks)
🔇 Additional comments (6)
pydantic_settings_export/generators/markdown.py (3)

1-1: Add import statement looks fine
This import is used properly to check for optional module availability.


5-5: Confirm Python version support for 'Self'
The Self type requires at least Python 3.11. Please verify the project's minimum version requirement is set accordingly to avoid compatibility issues in older environments.


114-122: Region validation logic
The check for text_region_parser is well-implemented and straightforward.

.pre-commit-config.yaml (1)

67-69: Additional dependencies for the hook
Adding [email,regions] aligns with the new regional injection feature. The change appears correct and consistent with the associated code.

pyproject.toml (2)

43-50: Review of Optional Dependencies Configuration

The new [project.optional-dependencies] section with the email and regions groups is correctly defined. The dependency constraints (email-validator>=2.2.0 and text-region-parser>=0.1.1) align with the feature requirements, ensuring that the optional dependencies are available when needed (as indicated by the updated pre-commit hook configuration). Please confirm documentation updates to reflect these new groups.


207-214: Review of Injected Markdown Generator Configuration

The new markdown generator configuration for "Injected Configuration" is well-formed. The added properties—table_only = true, file_prefix, and region = "config"—appear to implement the intended functionality for generating a basic table output in the designated documentation file. Please verify in the generator implementation that the region property is correctly handled as part of the conditional region-specific processing.

    - Correct grammar issues in CLI option descriptions for `.env` file loading.
    - Update documentation tables in `InjectedConfiguration.md`, `Configuration.md`, and `SimpleConfiguration.md` with corrected phrasing for `.env` descriptions.
    - Clarify content generation logic in `markdown.py` for table and region options.
@jag-k
Copy link
Owner Author

jag-k commented Feb 18, 2025

@coderabbitai review

Copy link

coderabbitai bot commented Feb 18, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
pydantic_settings_export/generators/markdown.py (1)

229-268: Consider optimizing region parsing implementation.

While the implementation is functionally correct, consider these improvements:

  1. Make the error message more specific about file requirements
  2. Initialize the region parser once for all files to improve performance
     def run(self, *settings_info: SettingsInfoModel) -> list[Path]:
         if not region:
             return super().run(*settings_info)
 
         import text_region_parser
 
         result = f"\n{self.generate(*settings_info).strip()}\n"
         file_paths = self.file_paths()
 
         constructor = text_region_parser.RegionConstructor()
+        # Initialize region parser once for all files
+        constructor.add_parser(cast(str, region))(lambda _: result)
 
-        # Add the region with name from the config
-        # This region will be replaced with the generated content
-        constructor.add_parser(cast(str, region))(lambda _: result)
-
         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."
+                    f"The file {path} must exist and contain region markers "
+                    f"<!-- region:{region} --> and <!-- endregion:{region} --> "
+                    f"before running the generator with the `region` option."
                 )
docs/Configuration.md (1)

9-9: Fix grammar in environment variable descriptions.

There are a few grammar issues in the documentation:

  1. Missing comma after "variables" in the sentence
  2. "then" should be "when"
  3. "require" should be "requires" to agree with "class/instance"
-The path to the `.env` file to load environment variables. Useful then you have a Settings class/instance, which require values while running.
+The path to the `.env` file to load environment variables, useful when you have a Settings class/instance, which requires values while running.

Also applies to: 16-16, 17-17

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 04242ce and ed89e66.

📒 Files selected for processing (5)
  • docs/Configuration.md (1 hunks)
  • docs/InjectedConfiguration.md (1 hunks)
  • docs/SimpleConfiguration.md (1 hunks)
  • pydantic_settings_export/cli.py (1 hunks)
  • pydantic_settings_export/generators/markdown.py (4 hunks)
✅ Files skipped from review due to trivial changes (1)
  • pydantic_settings_export/cli.py
🧰 Additional context used
🪛 LanguageTool
docs/Configuration.md

[uncategorized] ~17-~17: Possible missing comma found.
Context: ...nv` file to load environment variables. Useful then you have a Settings class/instance...

(AI_HYDRA_LEO_MISSING_COMMA)

docs/InjectedConfiguration.md

[uncategorized] ~16-~16: Possible missing comma found.
Context: ...nv` file to load environment variables. Useful then you have a Settings class/instance...

(AI_HYDRA_LEO_MISSING_COMMA)


[uncategorized] ~16-~16: This verb does not appear to agree with the subject. Consider using a different form.
Context: ...u have a Settings class/instance, which require values while running. | null ...

(AI_EN_LECTOR_REPLACEMENT_VERB_AGREEMENT)

docs/SimpleConfiguration.md

[uncategorized] ~9-~9: Possible missing comma found.
Context: ...nv` file to load environment variables. Useful then you have a Settings class/instance...

(AI_HYDRA_LEO_MISSING_COMMA)


[uncategorized] ~9-~9: This verb does not appear to agree with the subject. Consider using a different form.
Context: ...u have a Settings class/instance, which require values while running. | null ...

(AI_EN_LECTOR_REPLACEMENT_VERB_AGREEMENT)

🔇 Additional comments (4)
pydantic_settings_export/generators/markdown.py (2)

88-98: LGTM! Well-documented region field implementation.

The new region field is well-implemented with proper typing, clear documentation, and helpful examples.


116-124: LGTM! Robust optional dependency validation.

The validator correctly checks for the required dependency using importlib.util, providing a clear error message when the dependency is missing.

docs/InjectedConfiguration.md (1)

1-7: Clear Introduction for Injected Configuration

The introductory section clearly explains the concept of injected configuration along with the region markers. This helps set the context for users on how the generated configuration will replace the marked sections.

docs/SimpleConfiguration.md (1)

1-8: Effective Presentation of Configuration Table

The table layout in this documentation is well-structured and provides a clear, concise overview of each configuration setting. The headers and formatting align well with the project’s documentation style.

    - Corrected "then" to "when" in the description.
    - Fixed grammar: changed "which require values" to "which requires values."
@jag-k
Copy link
Owner Author

jag-k commented Feb 18, 2025

@coderabbitai review

Copy link

coderabbitai bot commented Feb 18, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@jag-k jag-k merged commit ceeeecc into dev Feb 18, 2025
2 checks passed
@jag-k jag-k deleted the feature/6-inject-config-to-markdown branch February 18, 2025 18:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature request New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add support to “inject” config to existed Markdown file
1 participant