Skip to content

Commit b6470e8

Browse files
marcleblanc2Amp
andcommitted
Support string patterns in config fields
Amp-Thread-ID: https://ampcode.com/threads/T-019e665f-205b-755b-ab28-497399c0e46d Co-authored-by: Amp <amp@ampcode.com>
1 parent 1cef701 commit b6470e8

3 files changed

Lines changed: 41 additions & 2 deletions

File tree

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,8 +87,8 @@ Config precedence is: code defaults, `.env`, shell environment, then CLI
8787
overrides. API client modules can provide shared Config base classes such as
8888
`LinearClientConfig`, and `parse_args` resolves `op://...` references by
8989
default. `config_field(default=...)` supports aliases, store-true /
90-
store-false command flags, optional values, and numeric bounds for simple
91-
CLIs. Pass a custom `argparse.ArgumentParser` to `parse_args` only when you
90+
store-false command flags, optional values, numeric bounds, and string patterns
91+
for simple CLIs. Pass a custom `argparse.ArgumentParser` to `parse_args` only when you
9292
need parsing beyond Config fields. Help text preserves description and
9393
argument-help newlines, and reserves enough option-column width for long config
9494
flags. Mark sensitive fields with `secret=True` so snapshots do not expose

src/src_py_lib/utils/config.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ def config_field(
9494
ge: int | float | None = None,
9595
lt: int | float | None = None,
9696
le: int | float | None = None,
97+
pattern: str | None = None,
9798
) -> Any:
9899
"""Return a Pydantic field with Config environment and CLI metadata."""
99100
option = ConfigOption(
@@ -121,6 +122,8 @@ def config_field(
121122
field_kwargs["lt"] = lt
122123
if le is not None:
123124
field_kwargs["le"] = le
125+
if pattern is not None:
126+
field_kwargs["pattern"] = pattern
124127
return Field(default, **field_kwargs)
125128

126129

tests/test_logging_http_clients.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,18 @@ class BoundedConfig(Config):
219219
)
220220

221221

222+
class PatternConfig(Config):
223+
"""Config model with a string pattern constraint."""
224+
225+
date: str | None = config_field(
226+
default=None,
227+
env_var="PATTERN_DATE",
228+
cli_flag="--date",
229+
metavar="YYYY-MM-DD",
230+
pattern=r"^\d{4}-\d{2}-\d{2}$",
231+
)
232+
233+
222234
class CommandStyleConfig(Config):
223235
"""Config model with command-style flags."""
224236

@@ -489,6 +501,30 @@ def test_config_field_supports_numeric_bounds(self) -> None:
489501
resolve_op_refs=False,
490502
)
491503

504+
def test_config_field_supports_string_pattern(self) -> None:
505+
config = load_config(
506+
PatternConfig,
507+
env_file=None,
508+
env={"PATTERN_DATE": "2026-01-31"},
509+
resolve_op_refs=False,
510+
)
511+
512+
self.assertEqual(config.date, "2026-01-31")
513+
with self.assertRaisesRegex(ConfigError, "String should match pattern"):
514+
load_config(
515+
PatternConfig,
516+
env_file=None,
517+
env={"PATTERN_DATE": "2026-1-31"},
518+
resolve_op_refs=False,
519+
)
520+
with self.assertRaisesRegex(ConfigError, "String should match pattern"):
521+
load_config(
522+
PatternConfig,
523+
env_file=None,
524+
env={"PATTERN_DATE": "2026-01-31T00:00:00Z"},
525+
resolve_op_refs=False,
526+
)
527+
492528
def test_logging_config_mixin_adds_log_level_from_cli_and_env(self) -> None:
493529
parser = argparse.ArgumentParser()
494530
add_config_arguments(parser, LoggingExampleConfig)

0 commit comments

Comments
 (0)