Source code for foundry.naming
"""Naming conventions and import-path helpers for code generation.
Provides :class:`Name` for deriving conventional identifiers
(PascalCase, snake_case, slugs) from a base string, plus helpers
for splitting and constructing Python dotted import paths.
"""
from __future__ import annotations
[docs]
class Name:
"""Derives conventional identifiers from a base string.
Accepts either a ``PascalCase`` class name (e.g. ``"Article"``)
or a ``snake_case`` identifier (e.g. ``"publish_article"``) and
exposes the common derived forms used by code generators.
Examples::
model = Name("Article")
model.pascal # "Article"
model.lower # "article"
model.suffixed("Resource") # "ArticleResource"
action = Name("publish_article")
action.pascal # "PublishArticle"
action.slug # "publish-article"
action.suffixed("Request") # "PublishArticleRequest"
"""
def __init__(self, raw: str) -> None: # noqa: D107
self.raw = raw
@property
def pascal(self) -> str:
"""PascalCase form of the name.
If the raw string contains no underscores and already
starts with an uppercase letter it is returned as-is
(assumed to already be PascalCase, e.g. ``"StockMovement"``
from a dotted import path).
"""
if "_" not in self.raw and self.raw[:1].isupper():
return self.raw
return "".join(part.capitalize() for part in self.raw.split("_"))
@property
def lower(self) -> str:
"""Fully lowercased form (for file/module names)."""
return self.raw.lower()
@property
def slug(self) -> str:
"""Hyphenated slug form (for URL segments)."""
return self.raw.replace("_", "-")
[docs]
def suffixed(self, suffix: str) -> str:
"""PascalCase name with *suffix* appended.
Args:
suffix: Class-name suffix, e.g. ``"CreateRequest"``.
Returns:
Combined string, e.g. ``"ArticleCreateRequest"``.
"""
return f"{self.pascal}{suffix}"
[docs]
@classmethod
def from_dotted(cls, dotted_path: str) -> tuple[str, Name]:
"""Create a :class:`Name` from a dotted import path.
Args:
dotted_path: A fully-qualified class path such as
``"myapp.models.Article"``.
Returns:
A ``(module, Name)`` tuple, e.g.
``("myapp.models", Name("Article"))``.
"""
module, class_name = split_dotted_class(dotted_path)
return module, cls(class_name)
[docs]
def split_dotted_class(dotted_path: str) -> tuple[str, str]:
"""Split a dotted import path into ``(module, class_name)``.
Args:
dotted_path: A fully-qualified class path such as
``"myapp.models.Article"``.
Returns:
A ``(module, class_name)`` tuple, e.g.
``("myapp.models", "Article")``.
Raises:
ValueError: If *dotted_path* contains fewer than two parts.
"""
if "." not in dotted_path:
msg = (
f"'{dotted_path}' is not a valid dotted import path. "
f"Expected 'module.ClassName', "
f"e.g. 'myapp.models.Article'."
)
raise ValueError(msg)
module, _, class_name = dotted_path.rpartition(".")
return module, class_name
[docs]
def prefix_import(prefix: str, *parts: str) -> str:
"""Build a Python import path under *prefix* (which may be empty).
Args:
prefix: Optional package prefix, e.g. ``"_generated"``.
*parts: Module name segments to join with ``.``.
Returns:
A ``.``-joined import path, with *prefix* prepended when
non-empty.
"""
if prefix:
return ".".join([prefix, *parts])
return ".".join(parts)