Skip to content

Commit

Permalink
Fix templates (#27)
Browse files Browse the repository at this point in the history
  • Loading branch information
radeklat authored Nov 7, 2023
1 parent 384618e commit f782dec
Show file tree
Hide file tree
Showing 7 changed files with 165 additions and 12 deletions.
17 changes: 16 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,20 @@ Types of changes are:

## [Unreleased]

## [3.1.1] - 2023-11-07

### Fixes

#### Markdown template

- Remove extra space between "Possible values" and "Examples" sections.

#### DotEnv template

- Fix wrapping of `possible_values`.
- Fix displaying of `possible_values` for tuples.
- Remove `None` as a default value.

## [3.1.0] - 2023-11-06

### Features
Expand Down Expand Up @@ -161,7 +175,8 @@ Add classifiers to the package.
- Initial release
[Unreleased]: https://github.com/radeklat/settings-doc/compare/3.1.0...HEAD
[Unreleased]: https://github.com/radeklat/settings-doc/compare/3.1.1...HEAD
[3.1.1]: https://github.com/radeklat/settings-doc/compare/3.1.0...3.1.1
[3.1.0]: https://github.com/radeklat/settings-doc/compare/3.0.1...3.1.0
[3.0.1]: https://github.com/radeklat/settings-doc/compare/3.0.0...3.0.1
[3.0.0]: https://github.com/radeklat/settings-doc/compare/2.1.0...3.0.0
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "settings-doc"
version = "3.1.0"
version = "3.1.1"
description = "A command line tool for generating Markdown documentation and .env files from pydantic BaseSettings."
authors = ["Radek Lát <[email protected]>"]
license = "MIT License"
Expand Down
17 changes: 10 additions & 7 deletions src/settings_doc/templates/dotenv.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,21 @@
{% if field.description %}
# {{ field.description|replace("\n", "\n# ") }}
{% endif %}
{% if field.examples %}
{% set possible_values = field.examples %}
{% elif is_typing_literal(field) %}
{% if is_typing_literal(field) %}
{% set possible_values = field.annotation.__args__ %}
{% elif field.json_schema_extra and "possible_values" in field.json_schema_extra %}
{% set possible_values = field.json_schema_extra.possible_values %}
{% endif %}
{% if possible_values %}
{% set possible_values_line = "`" + possible_values|join("`, `") + "`" %}
# Possible values:
{% if possible_values_line|length <= 75 %}
# {{ possible_values_line }}
{% if not is_values_with_descriptions(possible_values) %}
{% if possible_values|join("`, `")|length + 6 <= 75 %}
# `{{ possible_values|join("`, `") }}`
{% else %}
{% for value in possible_values %}
# - `{{ value }}`
{% endfor %}
{% endif %}
{% else %}
{% for value in possible_values %}
{% if value.__class__.__name__ == "tuple" and value|length <= 2 %}
Expand All @@ -37,7 +40,7 @@
{% endfor %}
{% endif %}
{% endif %}
{% if not field.is_required() %}# {% endif %}{{ env_name|upper }}={% if has_default_value(field) %}{{ field.default }}{% endif %}
{% if not field.is_required() %}# {% endif %}{{ env_name|upper }}={% if has_default_value(field) and field.default is not none %}{{ field.default }}{% endif %}


{% endfor %}
7 changes: 4 additions & 3 deletions src/settings_doc/templates/markdown.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

{{ field.description }}
{% endif %}
{# Possible values #}
{% if field.json_schema_extra and "possible_values" in field.json_schema_extra %}
{% set possible_values = field.json_schema_extra.possible_values %}
{% elif is_typing_literal(field) %}
Expand Down Expand Up @@ -49,11 +50,11 @@
- `{{ value }}`
{% endif %}
{% endfor %}

{% endif %}
{% endif %}
{# Example values #}
{% if field.json_schema_extra and "examples" in field.json_schema_extra %}
{# `field.json_schema_extra.examples` has no pre-defined structure, so it is more flexible #}
{# `field.json_schema_extra.examples` has no pre-defined structure, so it is more flexible #}
{% set examples_values = field.json_schema_extra.examples %}

{{ heading(2) }} Examples
Expand Down Expand Up @@ -82,7 +83,7 @@
{% endfor %}
{% endif %}
{% elif field.examples %}
{# `field.examples` is limited to a list of only example values #}
{# `field.examples` is limited to a list of only example values #}

{{ heading(2) }} Examples

Expand Down
7 changes: 7 additions & 0 deletions tests/fixtures/valid_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,13 @@ class ExamplesSettings(BaseSettings):
tuple_with_explanation: str = Field(
..., json_schema_extra={"examples": [("debug", "Debug level"), ("info", "Info level")]}
)
possible_values_and_examples: str = Field(
...,
json_schema_extra={
"possible_values": [("debug", "Debug level"), ("info", "Info level")],
"examples": [("debug", "Debug level"), ("info", "Info level")],
},
)


class RequiredSettings(BaseSettings):
Expand Down
115 changes: 115 additions & 0 deletions tests/unit/test_dotenv.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
from typing import Type

import pytest
from _pytest.logging import LogCaptureFixture
from pydantic import Field
from pydantic_settings import BaseSettings
from pytest_mock import MockerFixture
from typer.testing import CliRunner

from tests.fixtures.valid_settings import (
SETTINGS_ATTR,
ExamplesSettings,
FullSettings,
PossibleValuesSettings,
ValidationAliasChoicesSettings,
ValidationAliasPathSettings,
ValidationAliasSettings,
)
from tests.helpers import run_app_with_settings


class TestDotEnvFormat:
@staticmethod
@pytest.mark.parametrize(
"expected_string, settings_class",
[
pytest.param(f"{SETTINGS_ATTR}=\n", ValidationAliasSettings, id="validation alias with required value"),
pytest.param(
f"{SETTINGS_ATTR}=some_value\n\n", FullSettings, id="variable name with optional default value"
),
pytest.param("# use fullsettings like this\n", FullSettings, id="description"),
],
)
def should_generate(
runner: CliRunner,
mocker: MockerFixture,
expected_string: str,
settings_class: Type[BaseSettings],
):
assert expected_string in run_app_with_settings(mocker, runner, settings_class, fmt="dotenv")

@staticmethod
@pytest.mark.parametrize(
"expected_name, expected_string, settings_class",
[
pytest.param("literal", "`debug`, `info`", PossibleValuesSettings, id="literal"),
pytest.param(
"simple",
"`debug`, `info`",
PossibleValuesSettings,
id="a list in json_schema_extra.possible_values",
),
pytest.param(
"simple_tuple",
"- `debug`\n# - `info`",
PossibleValuesSettings,
id="tuples with a single item in json_schema_extra.possible_values",
),
pytest.param(
"tuple_with_explanation",
"- `debug`: debug level\n# - `info`: info level",
PossibleValuesSettings,
id="tuples with two items in json_schema_extra.possible_values",
),
],
)
def should_generate_possible_values_from(
runner: CliRunner,
mocker: MockerFixture,
expected_name: str,
expected_string: str,
settings_class: Type[BaseSettings],
):
assert f"# possible values:\n# {expected_string}\n{expected_name}=\n" in run_app_with_settings(
mocker, runner, settings_class, fmt="dotenv"
)

@staticmethod
def should_ignore_examples(runner: CliRunner, mocker: MockerFixture):
assert "# examples" not in run_app_with_settings(mocker, runner, ExamplesSettings, fmt="dotenv")

@staticmethod
@pytest.mark.parametrize(
"settings_class",
[
pytest.param(ValidationAliasPathSettings, id="validation AliasPath"),
pytest.param(ValidationAliasChoicesSettings, id="validation AliasChoices"),
],
)
def should_log_error_for(
runner: CliRunner,
mocker: MockerFixture,
settings_class: Type[BaseSettings],
caplog: LogCaptureFixture,
):
assert f"# {SETTINGS_ATTR}=" not in run_app_with_settings(mocker, runner, settings_class, fmt="dotenv")
assert "Unsupported validation alias" in caplog.text

@staticmethod
def should_list_values_if_they_do_not_fit_in_75_chars(runner: CliRunner, mocker: MockerFixture):
a_value = "a" * (75 - 2 - 4) # -2 chars. for backticks -4 for # and indentation
b_value = a_value.replace("a", "b") + "b" # 1 char over the limit

class Settings(BaseSettings):
fits: str = Field(..., json_schema_extra={"possible_values": [a_value]})
fits_not: str = Field(..., json_schema_extra={"possible_values": [b_value]})
not_values_and_descriptions: str = Field(
..., json_schema_extra={"possible_values": [(1, 2, 3), (4, 5, 6, 7)]}
)

stdout = run_app_with_settings(mocker, runner, Settings, fmt="dotenv")

assert f"# `{a_value}`\n" in stdout
assert f"# - `{b_value}`\n" in stdout
assert "# `(1, 2, 3)`, `(4, 5, 6, 7)`\n" in stdout
12 changes: 12 additions & 0 deletions tests/unit/test_markdown.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,18 @@ def should_generate_example_from(
mocker, runner, settings_class
)

@staticmethod
def should_generate_possible_values_and_examples(runner: CliRunner, mocker: MockerFixture):
expected_string = (
"# `possible_values_and_examples`\n\n"
"**required**\n\n"
"## possible values\n\n"
"- `debug`: debug level\n- `info`: info level\n\n"
"## examples\n\n"
"- `debug`: debug level\n- `info`: info level\n\n"
)
assert expected_string in run_app_with_settings(mocker, runner, ExamplesSettings)

@staticmethod
@pytest.mark.parametrize(
"settings_class",
Expand Down

0 comments on commit f782dec

Please sign in to comment.