Core Classes
Internal Click subclasses and configuration objects. Exposed publicly for advanced use
cases such as subclassing or type-checking.
PyclifOption
Extends click.Option with is_global and env-var binding support.
pyclif.PyclifOption
Bases: StoreInMetaMixin, Option
Custom Click Option that can be marked as global for propagation.
Source code in src/pyclif/core/classes.py
| class PyclifOption(StoreInMetaMixin, click_extra.Option):
"""Custom Click Option that can be marked as global for propagation."""
def __init__(self, *args: Any, is_global: bool = False, **kwargs: Any) -> None:
"""Initialize the option.
Args:
*args: Positional arguments for click.Option.
is_global: If True, this option will be propagated to subcommands.
**kwargs: Keyword arguments for click.Option.
"""
self.is_global = is_global
super().__init__(*args, **kwargs)
|
__init__(*args, is_global=False, **kwargs)
Initialize the option.
Parameters:
| Name |
Type |
Description |
Default |
*args
|
Any
|
Positional arguments for click.Option.
|
()
|
is_global
|
bool
|
If True, this option will be propagated to subcommands.
|
False
|
**kwargs
|
Any
|
Keyword arguments for click.Option.
|
{}
|
Source code in src/pyclif/core/classes.py
| def __init__(self, *args: Any, is_global: bool = False, **kwargs: Any) -> None:
"""Initialize the option.
Args:
*args: Positional arguments for click.Option.
is_global: If True, this option will be propagated to subcommands.
**kwargs: Keyword arguments for click.Option.
"""
self.is_global = is_global
super().__init__(*args, **kwargs)
|
PyclifGroup
Base Click group class used by app_group and group. Composes
HandleResponseMixin + GlobalOptionsMixin.
pyclif.PyclifGroup = PyclifExtraGroup
module-attribute
CustomConfigOption
Extends click-extra's config option with multi-location Linux config file search
(/etc/<app>/, ~/.config/<app>/, etc.).
pyclif.CustomConfigOption
Bases: StoreInMetaMixin, ConfigOption
Custom ConfigOption to add support for /etc/ on Linux systems.
This class extends the default click-extra ConfigOption to include system-wide
configuration directories following Linux conventions while maintaining
cross-platform compatibility.
Source code in src/pyclif/core/classes.py
| class CustomConfigOption(StoreInMetaMixin, ConfigOption):
"""Custom ConfigOption to add support for /etc/<cli_name> on Linux systems.
This class extends the default click-extra ConfigOption to include system-wide
configuration directories following Linux conventions while maintaining
cross-platform compatibility.
"""
def __init__(self, *args: Any, is_global: bool = False, **kwargs: Any) -> None:
"""Initialize the custom config option.
Args:
*args: Positional arguments.
is_global: If True, this option will be propagated to subcommands.
**kwargs: Keyword arguments.
"""
self.is_global = is_global
super().__init__(*args, **kwargs)
def get_default(self, ctx, call=True):
"""Override get_default to fix rich-click help rendering.
rich-click fetches the default with call=False during help generation,
which returns the raw bound method. We intercept this and force
evaluation to display the actual path cleanly.
"""
default = super().get_default(ctx, call=call)
# If call=False returned a callable (our bound method), evaluate it anyway
if not call and callable(default):
# noinspection PyBroadException
try:
return default()
except Exception:
pass
return default
def default_pattern(self) -> str:
"""Generate the default configuration search pattern.
Creates search patterns for configuration files, prioritizing Linux system
directories when running on Linux platforms. Falls back to standard
user configuration directories on other platforms.
Patterns are joined with "|" so that wcmatch's SPLIT flag (active on
click-extra's ConfigOption) treats each path as a separate glob target.
Returns:
The pipe-separated glob pattern covering all config locations.
Raises:
RuntimeError: If no click, context is available to determine CLI name.
"""
all_patterns = self._get_all_config_patterns()
if not all_patterns:
return self._get_fallback_pattern()
return "|".join(all_patterns)
def _get_extension_pattern(self) -> str:
"""Build a file extension pattern from supported formats.
Creates a glob-compatible extension pattern from the configured
formats, using either single extension or brace notation for
multiple extensions.
Returns:
str: Extension pattern for glob matching (e.g., 'toml' or '{toml,yaml,json}').
"""
# Build file extension pattern from supported formats
extensions = []
if self.file_format_patterns:
patterns = unique(flatten(self.file_format_patterns.values()))
# Keep only generic extensions (e.g., "*.toml" -> "toml")
# and ignore specific file patterns like "pyproject.toml"
extensions.extend(pat[2:] for pat in patterns if pat.startswith("*."))
extensions = unique([ext for ext in extensions if ext])
if not extensions:
return "*"
# Create an extension pattern for glob matching
if len(extensions) == 1:
return extensions[0]
else:
# Use brace notation for multiple extension matching
return f"{{{','.join(extensions)}}}"
def _get_all_config_patterns(self) -> list[str]:
"""Get all configuration search patterns in priority order.
Constructs file search patterns for different configuration locations
based on the current platform and supported file formats.
Returns:
List of glob patterns for configuration file search, ordered by
priority (system-wide first, then user-specific).
Raises:
RuntimeError: If no click, context is available to determine CLI name.
"""
patterns = []
# Get extension pattern
ext_pattern = self._get_extension_pattern()
# Get CLI name from click context
try:
ctx = get_current_context()
cli_name = ctx.find_root().info_name
except RuntimeError:
# No click context available - this can happen during testing
# or when called outside a click command context
return []
if not cli_name:
return []
# 1. Add a system-wide configuration path (Linux only)
if is_linux():
system_config_dir = Path(f"/etc/{cli_name}")
system_pattern = str(system_config_dir / f"*.{ext_pattern}")
patterns.append(system_pattern)
# 2. Add a user configuration directory (all platforms)
try:
roaming = getattr(self, "roaming", False)
force_posix = getattr(self, "force_posix", False)
app_dir = Path(
get_app_dir(cli_name, roaming=roaming, force_posix=force_posix)
).resolve()
user_pattern = str(app_dir / f"*.{ext_pattern}")
patterns.append(user_pattern)
except (OSError, ValueError, TypeError) as e:
# Handle specific exceptions that can be raised by get_app_dir or Path operations:
# - OSError: File system-related errors (permissions, path issues, etc.)
# - ValueError: Invalid arguments passed to get_app_dir or Path
# - TypeError: Type-related issues with arguments
import logging
# noinspection PyUnresolvedReferences
logger = logging.getLogger(__name__)
logger.debug(f"Failed to get user config directory: {e}")
# Continue without user config pattern - system config may still work
return patterns
def _get_fallback_pattern(self) -> str:
"""Get a fallback configuration pattern when normal detection fails.
Provides a basic configuration search pattern for cases where
the click context is not available or CLI name cannot be determined.
Returns:
str: A basic configuration file search pattern.
"""
# Basic fallback pattern in the current directory
ext_pattern = self._get_extension_pattern()
return f"*.{ext_pattern}"
|
__init__(*args, is_global=False, **kwargs)
Initialize the custom config option.
Parameters:
| Name |
Type |
Description |
Default |
*args
|
Any
|
|
()
|
is_global
|
bool
|
If True, this option will be propagated to subcommands.
|
False
|
**kwargs
|
Any
|
|
{}
|
Source code in src/pyclif/core/classes.py
| def __init__(self, *args: Any, is_global: bool = False, **kwargs: Any) -> None:
"""Initialize the custom config option.
Args:
*args: Positional arguments.
is_global: If True, this option will be propagated to subcommands.
**kwargs: Keyword arguments.
"""
self.is_global = is_global
super().__init__(*args, **kwargs)
|
get_default(ctx, call=True)
Override get_default to fix rich-click help rendering.
rich-click fetches the default with call=False during help generation,
which returns the raw bound method. We intercept this and force
evaluation to display the actual path cleanly.
Source code in src/pyclif/core/classes.py
| def get_default(self, ctx, call=True):
"""Override get_default to fix rich-click help rendering.
rich-click fetches the default with call=False during help generation,
which returns the raw bound method. We intercept this and force
evaluation to display the actual path cleanly.
"""
default = super().get_default(ctx, call=call)
# If call=False returned a callable (our bound method), evaluate it anyway
if not call and callable(default):
# noinspection PyBroadException
try:
return default()
except Exception:
pass
return default
|
default_pattern()
Generate the default configuration search pattern.
Creates search patterns for configuration files, prioritizing Linux system
directories when running on Linux platforms. Falls back to standard
user configuration directories on other platforms.
Patterns are joined with "|" so that wcmatch's SPLIT flag (active on
click-extra's ConfigOption) treats each path as a separate glob target.
Returns:
| Type |
Description |
str
|
The pipe-separated glob pattern covering all config locations.
|
Raises:
| Type |
Description |
RuntimeError
|
If no click, context is available to determine CLI name.
|
Source code in src/pyclif/core/classes.py
| def default_pattern(self) -> str:
"""Generate the default configuration search pattern.
Creates search patterns for configuration files, prioritizing Linux system
directories when running on Linux platforms. Falls back to standard
user configuration directories on other platforms.
Patterns are joined with "|" so that wcmatch's SPLIT flag (active on
click-extra's ConfigOption) treats each path as a separate glob target.
Returns:
The pipe-separated glob pattern covering all config locations.
Raises:
RuntimeError: If no click, context is available to determine CLI name.
"""
all_patterns = self._get_all_config_patterns()
if not all_patterns:
return self._get_fallback_pattern()
return "|".join(all_patterns)
|
PyclifTimerOption
Internal option class powering timer=True on @app_group. Subclasses click-extra's
TimerOption to integrate with pyclif's output format: suppresses the text echo in
json/yaml mode and injects timing fields into Response.data via returns_response.
pyclif.core.classes.PyclifTimerOption
Bases: TimerOption
TimerOption integrated with pyclif output format.
Skips the text echo in json/yaml mode — timing data is injected directly
into the Response by returns_response instead.
Source code in src/pyclif/core/classes.py
| class PyclifTimerOption(TimerOption):
"""TimerOption integrated with pyclif output format.
Skips the text echo in json/yaml mode — timing data is injected directly
into the Response by returns_response instead.
"""
# noinspection PyAttributeOutsideInit
def register_timer_on_close(
self, ctx: click_extra.Context, param: click_extra.Parameter, value: bool
) -> None:
"""Register the timer and store the context for deferred format check."""
if not value:
return
self.start_time = perf_counter()
self._close_ctx = ctx
ctx.meta["click_extra.start_time"] = self.start_time
ctx.call_on_close(self.print_timer)
def print_timer(self) -> None:
"""Print elapsed time unless the output format is json or yaml.
Output format is read at close time so that eager option processing
order does not matter — meta is fully populated by then.
"""
output_format = self._close_ctx.find_root().meta.get("pyclif.output_format", "table")
if output_format in ("json", "yaml"):
return
elapsed = perf_counter() - self.start_time
click_extra.echo(f"Execution time: {elapsed:.3f} seconds.")
|
register_timer_on_close(ctx, param, value)
Register the timer and store the context for deferred format check.
Source code in src/pyclif/core/classes.py
| def register_timer_on_close(
self, ctx: click_extra.Context, param: click_extra.Parameter, value: bool
) -> None:
"""Register the timer and store the context for deferred format check."""
if not value:
return
self.start_time = perf_counter()
self._close_ctx = ctx
ctx.meta["click_extra.start_time"] = self.start_time
ctx.call_on_close(self.print_timer)
|
print_timer()
Print elapsed time unless the output format is json or yaml.
Output format is read at close time so that eager option processing
order does not matter — meta is fully populated by then.
Source code in src/pyclif/core/classes.py
| def print_timer(self) -> None:
"""Print elapsed time unless the output format is json or yaml.
Output format is read at close time so that eager option processing
order does not matter — meta is fully populated by then.
"""
output_format = self._close_ctx.find_root().meta.get("pyclif.output_format", "table")
if output_format in ("json", "yaml"):
return
elapsed = perf_counter() - self.start_time
click_extra.echo(f"Execution time: {elapsed:.3f} seconds.")
|