CLI Input Types and Resource Identifiers
CLI Input Types and Resource Identifiers
The primary purpose of CLI Input Types and Resource Identifiers is to provide a robust, standardized, and flexible mechanism for interpreting diverse user inputs into actionable, type-safe resource identifiers within command-line interfaces. This system abstracts the complexities of input parsing, validation, and contextual resolution, enabling developers to build CLIs that are intuitive for users and resilient to varied input formats. It ensures that regardless of how a user specifies a resource—be it by ID, name, path, or configuration file—the underlying application receives a consistent and validated representation.
Core Features
The system offers several core features designed to streamline CLI input handling:
- Flexible Input Parsing: Supports a wide array of input formats, including direct string identifiers, file paths (for JSON/YAML configurations), environment variables, and structured key-value pairs. This flexibility allows users to interact with the CLI using their preferred method.
- Standardized Resource Identification: Converts disparate input types into a uniform internal
ResourceIdobject. This object encapsulates the resource's unique identifier, type, and any relevant metadata, ensuring consistency across the application's internal logic. - Type Coercion and Validation: Automatically attempts to coerce input values into the expected data types (e.g., integer IDs, UUIDs, specific string patterns). It performs validation against predefined schemas or rules, rejecting malformed inputs early in the command execution lifecycle.
- Contextual Resolution: Resolves ambiguous or partial resource identifiers based on the current operational context. For instance, a command might automatically infer a "current project" or "active service" if not explicitly provided, using environment variables, configuration files, or working directory context.
- Extensibility: Provides clear extension points for defining custom input types and resolution strategies. Developers can register new
InputTypeHandlerimplementations to support unique domain-specific input formats or integrate with external systems for resource lookup.
Common Use Cases
Developers leverage CLI Input Types and Resource Identifiers in various scenarios to enhance user experience and simplify command implementation:
-
Direct Resource Manipulation: Users often specify resources directly by their unique ID or a human-readable name. The system parses these inputs and resolves them to the corresponding
ResourceId.# Example: Deleting a project by ID
# mycli delete --project-id 12345
project_id_str = cli_args.get("project_id")
project_identifier = InputParser.parse(project_id_str, expected_type=ProjectResourceId)
# project_identifier is now a validated ProjectResourceId object -
Configuration via Files: For complex resource creation or updates, users can provide configuration details in JSON or YAML files. The system reads and validates these files, extracting resource identifiers and properties.
# Example: Creating a resource from a YAML file
# mycli create --config-file my_resource_config.yaml
config_file_path = cli_args.get("config_file")
config_data = InputParser.parse_file(config_file_path, format="yaml")
# config_data contains the parsed YAML, which might include resource identifiers -
Environment Variable Integration: Many CI/CD pipelines and scripting environments rely on environment variables. The system can be configured to resolve resource identifiers from specified environment variables if command-line arguments are absent.
# Example: Deploying to a project specified by an environment variable
# MY_PROJECT_ID=proj-abc mycli deploy
project_id = InputParser.resolve_from_env("MY_PROJECT_ID", expected_type=ProjectResourceId)
# project_id is a ProjectResourceId object -
Contextual Resource Selection: Commands that operate on a "current" or "active" resource benefit from contextual resolution. For example, a
logscommand might automatically target the service deployed in the current working directory.# Example: Viewing logs for the current service
# mycli logs
current_service_id = ResourceResolver.resolve_current_service()
# current_service_id is a ServiceResourceId object inferred from context
Implementation Details and Best Practices
Integrating CLI Input Types and Resource Identifiers involves defining resource types, utilizing the InputParser for argument processing, and potentially extending the system for custom needs.
Defining Resource Identifiers
Resource identifiers are represented by the ResourceId base class. Developers typically subclass ResourceId to create specific types for their application's resources, such as ProjectResourceId, ServiceResourceId, or UserResourceId. These subclasses can enforce specific validation rules or provide convenience methods.
from my_cli_system.identifiers import ResourceId, UUIDIdentifier
class ProjectResourceId(ResourceId):
"""Represents a unique project identifier."""
def __init__(self, project_uuid: str):
super().__init__(identifier=UUIDIdentifier(project_uuid), resource_type="project")
# Additional project-specific validation can go here
class ServiceResourceId(ResourceId):
"""Represents a unique service identifier, potentially with a project context."""
def __init__(self, service_name: str, project_id: ProjectResourceId = None):
super().__init__(identifier=service_name, resource_type="service")
self.project_id = project_id
# Validate service_name format, etc.
Using the InputParser
The InputParser utility is the primary entry point for converting raw CLI arguments into ResourceId objects or other structured data. It exposes methods like parse, parse_file, and resolve_from_env.
from my_cli_system.parser import InputParser
from my_cli_system.resolver import ResourceResolver
from my_cli_system.identifiers import ProjectResourceId, ServiceResourceId
def handle_delete_command(args):
# Parse a project ID, allowing either a UUID string or a file path
project_id_input = args.get("--project")
if project_id_input:
project_id = InputParser.parse(project_id_input, expected_type=ProjectResourceId)
else:
# Fallback to contextual resolution if not provided
project_id = ResourceResolver.resolve_current_project()
# Parse a service name, which might be a simple string or a name from a config file
service_name_input = args.get("--service")
service_id = InputParser.parse(service_name_input, expected_type=ServiceResourceId, context={"project": project_id})
# Proceed with deletion using the validated resource identifiers
print(f"Deleting service '{service_id.identifier}' in project '{project_id.identifier}'")
# ... actual deletion logic ...
The InputParser.parse() method is intelligent; it attempts to infer the input type (e.g., UUID, file path) and apply the appropriate InputTypeHandler based on the expected_type and its internal registry.
Extending Input Types with InputTypeHandler
For custom input formats or integration with external systems, developers can implement the InputTypeHandler interface and register it with the InputParser. This allows the system to understand new ways of specifying resources.
from my_cli_system.parser import InputParser, InputTypeHandler
from my_cli_system.identifiers import ResourceId
class MyApiResource(ResourceId):
"""Represents a resource from a specific API."""
def __init__(self, resource_path: str):
super().__init__(identifier=resource_path, resource_type="my_api_resource")
class CustomUrlInputHandler(InputTypeHandler):
"""Handles resource identifiers provided as specific URLs."""
def can_handle(self, raw_input: str, expected_type: type[ResourceId]) -> bool:
return expected_type == MyApiResource and raw_input.startswith("https://myapi.com/resources/")
def handle(self, raw_input: str, expected_type: type[ResourceId]) -> ResourceId:
resource_path = raw_input.split("https://myapi.com/resources/")[1]
# Perform lookup or direct parsing from the path
return MyApiResource(resource_path)
# Register the custom handler during application startup
InputParser.register_handler(CustomUrlInputHandler())
Performance Considerations
- Caching: For
ResourceResolverimplementations that involve network calls or expensive file system operations (e.g., looking up resources in a remote registry or parsing large configuration files), implement caching strategies. TheResourceResolvercan internally cache frequently accessed contextual data. - Lazy Loading: When dealing with file-based inputs, especially large ones, consider lazy loading or streaming approaches if only parts of the file are needed initially. The
InputParser.parse_filemethod can support options for partial parsing. - Pre-validation: Perform basic syntax validation on inputs as early as possible to avoid expensive processing of invalid data.
Limitations and Important Considerations
- Ambiguity in Contextual Resolution: Overly broad or poorly defined contextual resolution rules can lead to unexpected behavior. Design
ResourceResolverimplementations carefully, providing clear precedence rules and user feedback when ambiguity arises. - Security of File and Environment Inputs: When accepting file paths or environment variables, ensure proper sanitization and validation to prevent path traversal vulnerabilities, injection attacks, or exposure of sensitive information. The system provides mechanisms for validation, but developers must define appropriate schemas and checks.
- Complexity of Custom Handlers: While extensible, overly complex
InputTypeHandlerimplementations can make the system harder to debug and maintain. Strive for handlers that are focused on a single input type or resolution logic. - Error Reporting: Ensure that parsing and resolution errors provide clear, actionable feedback to the user, guiding them on how to correct their input. The system's exception types (e.g.,
InvalidInputError,ResourceNotFoundError) should be caught and translated into user-friendly messages.