# cjm-plugin-system
<!-- WARNING: THIS FILE WAS AUTOGENERATED! DO NOT EDIT! -->
## Install
``` bash
pip install cjm_plugin_system
```
## Project Structure
nbs/
├── core/ (3)
│ ├── interface.ipynb # Abstract base class defining the generic plugin interface
│ ├── manager.ipynb # Plugin discovery, loading, and lifecycle management system
│ └── metadata.ipynb # Data structures for plugin metadata
└── utils/ (1)
└── validation.ipynb # JSON Schema validation helpers for plugin configuration
Total: 4 notebooks across 2 directories
## Module Dependencies
``` mermaid
graph LR
core_interface[core.interface<br/>Plugin Interface]
core_manager[core.manager<br/>Plugin Manager]
core_metadata[core.metadata<br/>Plugin Metadata]
utils_validation[utils.validation<br/>Configuration Validation]
core_interface --> utils_validation
core_manager --> core_metadata
core_manager --> core_interface
```
*3 cross-module dependencies detected*
## CLI Reference
No CLI commands found in this project.
## Module Overview
Detailed documentation for each module in the project:
### Plugin Interface (`interface.ipynb`)
> Abstract base class defining the generic plugin interface
#### Import
``` python
from cjm_plugin_system.core.interface import (
PluginInterface,
PluginInterface_supports_streaming,
PluginInterface_execute_stream
)
```
#### Functions
``` python
def PluginInterface_supports_streaming(self) -> bool: # True if execute_stream is implemented
"""Check if this plugin supports streaming execution."""
# Default: check if execute_stream is overridden from the base class
"Check if this plugin supports streaming execution."
```
``` python
def PluginInterface_execute_stream(
self,
*args, # Arguments for plugin execution
**kwargs # Keyword arguments for plugin execution
) -> Generator[Any, None, Any]: # Yields partial results, returns final result
"Stream execution results chunk by chunk."
```
#### Classes
``` python
class PluginInterface(ABC):
"Generic plugin interface that all plugins must implement."
def name(self) -> str: # Unique identifier for this plugin
"""Unique plugin identifier."""
pass
@property
@abstractmethod
def version(self) -> str: # Semantic version string (e.g., "1.0.0")
"Unique plugin identifier."
def version(self) -> str: # Semantic version string (e.g., "1.0.0")
"""Plugin version."""
pass
@abstractmethod
def initialize(
self,
config:Optional[Dict[str, Any]]=None # Configuration dictionary for plugin-specific settings
) -> None
"Plugin version."
def initialize(
self,
config:Optional[Dict[str, Any]]=None # Configuration dictionary for plugin-specific settings
) -> None
"Initialize the plugin with configuration."
def execute(
self,
*args,
**kwargs
) -> Any: # Plugin-specific output
"Execute the plugin's main functionality."
def is_available(self) -> bool: # True if all required dependencies are available
"""Check if the plugin's dependencies are available."""
pass
@staticmethod
@abstractmethod
def get_config_schema() -> Dict[str, Any]: # JSON Schema describing configuration options
"Check if the plugin's dependencies are available."
def get_config_schema() -> Dict[str, Any]: # JSON Schema describing configuration options
"""Return JSON Schema describing the plugin's configuration options."""
pass
@abstractmethod
def get_current_config(self) -> Dict[str, Any]: # Current configuration state
"Return JSON Schema describing the plugin's configuration options."
def get_current_config(self) -> Dict[str, Any]: # Current configuration state
"""Return the current configuration state."""
pass
def validate_config(
self,
config:Dict[str, Any] # Configuration to validate
) -> Tuple[bool, Optional[str]]: # (is_valid, error_message)
"Return the current configuration state."
def validate_config(
self,
config:Dict[str, Any] # Configuration to validate
) -> Tuple[bool, Optional[str]]: # (is_valid, error_message)
"Validate a configuration dictionary against the schema."
def get_config_defaults(self) -> Dict[str, Any]: # Default values from schema
"""Extract default values from the configuration schema."""
schema = self.get_config_schema()
return extract_defaults(schema)
def cleanup(self) -> None
"Extract default values from the configuration schema."
def cleanup(self) -> None
"Optional cleanup when plugin is unloaded."
```
### Plugin Manager (`manager.ipynb`)
> Plugin discovery, loading, and lifecycle management system
#### Import
``` python
from cjm_plugin_system.core.manager import (
PluginManager,
get_plugin_config_schema,
get_plugin_config,
update_plugin_config,
validate_plugin_config,
get_all_plugin_schemas,
reload_plugin,
execute_plugin_stream,
check_streaming_support,
get_streaming_plugins
)
```
#### Functions
``` python
def get_plugin_config_schema(
self,
plugin_name:str # Name of the plugin
) -> Optional[Dict[str, Any]]: # Configuration schema or None if plugin not found
"Get the configuration schema for a plugin."
```
``` python
def get_plugin_config(
self,
plugin_name:str # Name of the plugin
) -> Optional[Dict[str, Any]]: # Current configuration or None if plugin not found
"Get the current configuration of a plugin."
```
``` python
def update_plugin_config(
self,
plugin_name:str, # Name of the plugin
config:Dict[str, Any], # New configuration
merge:bool=True # Whether to merge with existing config or replace entirely
) -> bool: # True if successful, False otherwise
"Update a plugin's configuration and reinitialize it."
```
``` python
def validate_plugin_config(
self,
plugin_name:str, # Name of the plugin
config:Dict[str, Any] # Configuration to validate
) -> Tuple[bool, Optional[str]]: # (is_valid, error_message)
"Validate a configuration dictionary for a plugin without applying it."
```
``` python
def get_all_plugin_schemas(
self
) -> Dict[str, Dict[str, Any]]: # Dictionary mapping plugin names to their schemas
"Get configuration schemas for all loaded plugins."
```
``` python
def reload_plugin(
self,
plugin_name:str, # Name of the plugin to reload
config:Optional[Dict[str, Any]]=None # Optional new configuration
) -> bool: # True if successful, False otherwise
"Reload a plugin with optional new configuration."
```
``` python
def execute_plugin_stream(
self,
plugin_name:str, # Name of the plugin to execute
*args, # Arguments to pass to the plugin
**kwargs # Keyword arguments to pass to the plugin
) -> Generator[Any, None, Any]: # Generator yielding partial results, returns final result
"Execute a plugin with streaming support if available."
```
``` python
def check_streaming_support(
self,
plugin_name:str # Name of the plugin to check
) -> bool: # True if plugin supports streaming
"Check if a plugin supports streaming execution."
```
``` python
def get_streaming_plugins(
self
) -> List[str]: # List of plugin names that support streaming
"Get a list of all loaded plugins that support streaming."
```
#### Classes
``` python
class PluginManager:
def __init__(
self,
plugin_interface:Type[PluginInterface]=PluginInterface, # Base class/interface plugins must implement
entry_point_group:Optional[str]=None # Optional override for entry point group name
)
"Manages plugin discovery, loading, and lifecycle."
def __init__(
self,
plugin_interface:Type[PluginInterface]=PluginInterface, # Base class/interface plugins must implement
entry_point_group:Optional[str]=None # Optional override for entry point group name
)
"Initialize the plugin manager."
def get_entry_points(self) -> importlib.metadata.EntryPoints: # Entry points for the configured group
"""Get plugin entry points from installed packages."""
self.entry_points = []
try
"Get plugin entry points from installed packages."
def discover_plugins(self) -> List[PluginMeta]: # List of discovered plugin metadata objects
"""Discover all installed plugins via entry points."""
self.discovered = []
for ep in self.entry_points
"Discover all installed plugins via entry points."
def load_plugin(
self,
plugin_meta:PluginMeta, # Plugin metadata
config:Optional[Dict[str, Any]]=None # Optional configuration for the plugin
) -> bool: # True if successfully loaded, False otherwise
"Load and initialize a plugin."
def load_plugin_from_module(
self,
module_path:str, # Path to the Python module
config:Optional[Dict[str, Any]]=None # Optional configuration for the plugin
) -> bool: # True if successfully loaded, False otherwise
"Load a plugin directly from a Python module file or package."
def unload_plugin(
self,
plugin_name:str # Name of the plugin to unload
) -> bool: # True if successfully unloaded, False otherwise
"Unload a plugin and call its cleanup method."
def get_plugin(
self,
plugin_name:str # Name of the plugin to retrieve
) -> Optional[PluginInterface]: # Plugin instance if found, None otherwise
"Get a loaded plugin instance by name."
def list_plugins(self) -> List[PluginMeta]: # List of metadata for all loaded plugins
"""List all loaded plugins."""
return list(self.plugins.values())
def execute_plugin(
self,
plugin_name:str, # Name of the plugin to execute
*args, # Arguments to pass to the plugin
**kwargs # Keyword arguments to pass to the plugin
) -> Any: # Result of the plugin execution
"List all loaded plugins."
def execute_plugin(
self,
plugin_name:str, # Name of the plugin to execute
*args, # Arguments to pass to the plugin
**kwargs # Keyword arguments to pass to the plugin
) -> Any: # Result of the plugin execution
"Execute a plugin's main functionality."
def enable_plugin(
self,
plugin_name:str # Name of the plugin to enable
) -> bool: # True if plugin was enabled, False if not found
"Enable a plugin."
def disable_plugin(
self,
plugin_name:str # Name of the plugin to disable
) -> bool: # True if plugin was disabled, False if not found
"Disable a plugin without unloading it."
```
### Plugin Metadata (`metadata.ipynb`)
> Data structures for plugin metadata
#### Import
``` python
from cjm_plugin_system.core.metadata import (
PluginMeta
)
```
#### Classes
``` python
@dataclass
class PluginMeta:
"Metadata about a plugin."
name: str # Plugin's unique identifier
version: str # Plugin's version string
description: str = '' # Brief description of the plugin's functionality
author: str = '' # Plugin author's name or organization
package_name: str = '' # Python package name containing the plugin
instance: Optional[Any] # Plugin instance (PluginInterface subclass)
enabled: bool = True # Whether the plugin is enabled
```
### Configuration Validation (`validation.ipynb`)
> JSON Schema validation helpers for plugin configuration
#### Import
``` python
from cjm_plugin_system.utils.validation import (
validate_config,
extract_defaults
)
```
#### Functions
``` python
def validate_config(
config:Dict[str, Any], # Configuration to validate
schema:Dict[str, Any] # JSON Schema to validate against
) -> Tuple[bool, Optional[str]]: # (is_valid, error_message)
"Validate a configuration dictionary against a JSON Schema."
```
``` python
def _basic_validate(
config:Dict[str, Any], # Configuration to validate
schema:Dict[str, Any] # JSON Schema to validate against
) -> Tuple[bool, Optional[str]]: # (is_valid, error_message)
"Basic validation without jsonschema library."
```
``` python
def extract_defaults(
schema:Dict[str, Any] # JSON Schema
) -> Dict[str, Any]: # Default values from schema
"Extract default values from a JSON Schema."
```
Raw data
{
"_id": null,
"home_page": "https://github.com/cj-mills/cjm-plugin-system",
"name": "cjm-plugin-system",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.9",
"maintainer_email": null,
"keywords": "nbdev jupyter notebook python",
"author": "Christian J. Mills",
"author_email": "9126128+cj-mills@users.noreply.github.com",
"download_url": "https://files.pythonhosted.org/packages/94/61/b3eb7811edda5a13e7ba8bbc727d86780342c57c7ac761de0a5b2c054a36/cjm_plugin_system-0.0.4.tar.gz",
"platform": null,
"description": "# cjm-plugin-system\n\n\n<!-- WARNING: THIS FILE WAS AUTOGENERATED! DO NOT EDIT! -->\n\n## Install\n\n``` bash\npip install cjm_plugin_system\n```\n\n## Project Structure\n\n nbs/\n \u251c\u2500\u2500 core/ (3)\n \u2502 \u251c\u2500\u2500 interface.ipynb # Abstract base class defining the generic plugin interface\n \u2502 \u251c\u2500\u2500 manager.ipynb # Plugin discovery, loading, and lifecycle management system\n \u2502 \u2514\u2500\u2500 metadata.ipynb # Data structures for plugin metadata\n \u2514\u2500\u2500 utils/ (1)\n \u2514\u2500\u2500 validation.ipynb # JSON Schema validation helpers for plugin configuration\n\nTotal: 4 notebooks across 2 directories\n\n## Module Dependencies\n\n``` mermaid\ngraph LR\n core_interface[core.interface<br/>Plugin Interface]\n core_manager[core.manager<br/>Plugin Manager]\n core_metadata[core.metadata<br/>Plugin Metadata]\n utils_validation[utils.validation<br/>Configuration Validation]\n\n core_interface --> utils_validation\n core_manager --> core_metadata\n core_manager --> core_interface\n```\n\n*3 cross-module dependencies detected*\n\n## CLI Reference\n\nNo CLI commands found in this project.\n\n## Module Overview\n\nDetailed documentation for each module in the project:\n\n### Plugin Interface (`interface.ipynb`)\n\n> Abstract base class defining the generic plugin interface\n\n#### Import\n\n``` python\nfrom cjm_plugin_system.core.interface import (\n PluginInterface,\n PluginInterface_supports_streaming,\n PluginInterface_execute_stream\n)\n```\n\n#### Functions\n\n``` python\ndef PluginInterface_supports_streaming(self) -> bool: # True if execute_stream is implemented\n \"\"\"Check if this plugin supports streaming execution.\"\"\"\n # Default: check if execute_stream is overridden from the base class\n \"Check if this plugin supports streaming execution.\"\n```\n\n``` python\ndef PluginInterface_execute_stream(\n self,\n *args, # Arguments for plugin execution\n **kwargs # Keyword arguments for plugin execution\n) -> Generator[Any, None, Any]: # Yields partial results, returns final result\n \"Stream execution results chunk by chunk.\"\n```\n\n#### Classes\n\n``` python\nclass PluginInterface(ABC):\n \"Generic plugin interface that all plugins must implement.\"\n \n def name(self) -> str: # Unique identifier for this plugin\n \"\"\"Unique plugin identifier.\"\"\"\n pass\n \n @property\n @abstractmethod\n def version(self) -> str: # Semantic version string (e.g., \"1.0.0\")\n \"Unique plugin identifier.\"\n \n def version(self) -> str: # Semantic version string (e.g., \"1.0.0\")\n \"\"\"Plugin version.\"\"\"\n pass\n \n @abstractmethod\n def initialize(\n self,\n config:Optional[Dict[str, Any]]=None # Configuration dictionary for plugin-specific settings\n ) -> None\n \"Plugin version.\"\n \n def initialize(\n self,\n config:Optional[Dict[str, Any]]=None # Configuration dictionary for plugin-specific settings\n ) -> None\n \"Initialize the plugin with configuration.\"\n \n def execute(\n self,\n *args,\n **kwargs\n ) -> Any: # Plugin-specific output\n \"Execute the plugin's main functionality.\"\n \n def is_available(self) -> bool: # True if all required dependencies are available\n \"\"\"Check if the plugin's dependencies are available.\"\"\"\n pass\n \n @staticmethod\n @abstractmethod\n def get_config_schema() -> Dict[str, Any]: # JSON Schema describing configuration options\n \"Check if the plugin's dependencies are available.\"\n \n def get_config_schema() -> Dict[str, Any]: # JSON Schema describing configuration options\n \"\"\"Return JSON Schema describing the plugin's configuration options.\"\"\"\n pass\n \n @abstractmethod\n def get_current_config(self) -> Dict[str, Any]: # Current configuration state\n \"Return JSON Schema describing the plugin's configuration options.\"\n \n def get_current_config(self) -> Dict[str, Any]: # Current configuration state\n \"\"\"Return the current configuration state.\"\"\"\n pass\n \n def validate_config(\n self,\n config:Dict[str, Any] # Configuration to validate\n ) -> Tuple[bool, Optional[str]]: # (is_valid, error_message)\n \"Return the current configuration state.\"\n \n def validate_config(\n self,\n config:Dict[str, Any] # Configuration to validate\n ) -> Tuple[bool, Optional[str]]: # (is_valid, error_message)\n \"Validate a configuration dictionary against the schema.\"\n \n def get_config_defaults(self) -> Dict[str, Any]: # Default values from schema\n \"\"\"Extract default values from the configuration schema.\"\"\"\n schema = self.get_config_schema()\n return extract_defaults(schema)\n \n def cleanup(self) -> None\n \"Extract default values from the configuration schema.\"\n \n def cleanup(self) -> None\n \"Optional cleanup when plugin is unloaded.\"\n```\n\n### Plugin Manager (`manager.ipynb`)\n\n> Plugin discovery, loading, and lifecycle management system\n\n#### Import\n\n``` python\nfrom cjm_plugin_system.core.manager import (\n PluginManager,\n get_plugin_config_schema,\n get_plugin_config,\n update_plugin_config,\n validate_plugin_config,\n get_all_plugin_schemas,\n reload_plugin,\n execute_plugin_stream,\n check_streaming_support,\n get_streaming_plugins\n)\n```\n\n#### Functions\n\n``` python\ndef get_plugin_config_schema(\n self,\n plugin_name:str # Name of the plugin\n) -> Optional[Dict[str, Any]]: # Configuration schema or None if plugin not found\n \"Get the configuration schema for a plugin.\"\n```\n\n``` python\ndef get_plugin_config(\n self,\n plugin_name:str # Name of the plugin\n) -> Optional[Dict[str, Any]]: # Current configuration or None if plugin not found\n \"Get the current configuration of a plugin.\"\n```\n\n``` python\ndef update_plugin_config(\n self,\n plugin_name:str, # Name of the plugin\n config:Dict[str, Any], # New configuration\n merge:bool=True # Whether to merge with existing config or replace entirely\n) -> bool: # True if successful, False otherwise\n \"Update a plugin's configuration and reinitialize it.\"\n```\n\n``` python\ndef validate_plugin_config(\n self,\n plugin_name:str, # Name of the plugin\n config:Dict[str, Any] # Configuration to validate\n) -> Tuple[bool, Optional[str]]: # (is_valid, error_message)\n \"Validate a configuration dictionary for a plugin without applying it.\"\n```\n\n``` python\ndef get_all_plugin_schemas(\n self\n) -> Dict[str, Dict[str, Any]]: # Dictionary mapping plugin names to their schemas\n \"Get configuration schemas for all loaded plugins.\"\n```\n\n``` python\ndef reload_plugin(\n self,\n plugin_name:str, # Name of the plugin to reload\n config:Optional[Dict[str, Any]]=None # Optional new configuration\n) -> bool: # True if successful, False otherwise\n \"Reload a plugin with optional new configuration.\"\n```\n\n``` python\ndef execute_plugin_stream(\n self,\n plugin_name:str, # Name of the plugin to execute\n *args, # Arguments to pass to the plugin\n **kwargs # Keyword arguments to pass to the plugin\n) -> Generator[Any, None, Any]: # Generator yielding partial results, returns final result\n \"Execute a plugin with streaming support if available.\"\n```\n\n``` python\ndef check_streaming_support(\n self,\n plugin_name:str # Name of the plugin to check\n) -> bool: # True if plugin supports streaming\n \"Check if a plugin supports streaming execution.\"\n```\n\n``` python\ndef get_streaming_plugins(\n self\n) -> List[str]: # List of plugin names that support streaming\n \"Get a list of all loaded plugins that support streaming.\"\n```\n\n#### Classes\n\n``` python\nclass PluginManager:\n def __init__(\n self,\n plugin_interface:Type[PluginInterface]=PluginInterface, # Base class/interface plugins must implement\n entry_point_group:Optional[str]=None # Optional override for entry point group name\n )\n \"Manages plugin discovery, loading, and lifecycle.\"\n \n def __init__(\n self,\n plugin_interface:Type[PluginInterface]=PluginInterface, # Base class/interface plugins must implement\n entry_point_group:Optional[str]=None # Optional override for entry point group name\n )\n \"Initialize the plugin manager.\"\n \n def get_entry_points(self) -> importlib.metadata.EntryPoints: # Entry points for the configured group\n \"\"\"Get plugin entry points from installed packages.\"\"\"\n self.entry_points = []\n try\n \"Get plugin entry points from installed packages.\"\n \n def discover_plugins(self) -> List[PluginMeta]: # List of discovered plugin metadata objects\n \"\"\"Discover all installed plugins via entry points.\"\"\"\n self.discovered = []\n \n for ep in self.entry_points\n \"Discover all installed plugins via entry points.\"\n \n def load_plugin(\n self,\n plugin_meta:PluginMeta, # Plugin metadata\n config:Optional[Dict[str, Any]]=None # Optional configuration for the plugin\n ) -> bool: # True if successfully loaded, False otherwise\n \"Load and initialize a plugin.\"\n \n def load_plugin_from_module(\n self,\n module_path:str, # Path to the Python module\n config:Optional[Dict[str, Any]]=None # Optional configuration for the plugin\n ) -> bool: # True if successfully loaded, False otherwise\n \"Load a plugin directly from a Python module file or package.\"\n \n def unload_plugin(\n self,\n plugin_name:str # Name of the plugin to unload\n ) -> bool: # True if successfully unloaded, False otherwise\n \"Unload a plugin and call its cleanup method.\"\n \n def get_plugin(\n self,\n plugin_name:str # Name of the plugin to retrieve\n ) -> Optional[PluginInterface]: # Plugin instance if found, None otherwise\n \"Get a loaded plugin instance by name.\"\n \n def list_plugins(self) -> List[PluginMeta]: # List of metadata for all loaded plugins\n \"\"\"List all loaded plugins.\"\"\"\n return list(self.plugins.values())\n \n def execute_plugin(\n self,\n plugin_name:str, # Name of the plugin to execute\n *args, # Arguments to pass to the plugin\n **kwargs # Keyword arguments to pass to the plugin\n ) -> Any: # Result of the plugin execution\n \"List all loaded plugins.\"\n \n def execute_plugin(\n self,\n plugin_name:str, # Name of the plugin to execute\n *args, # Arguments to pass to the plugin\n **kwargs # Keyword arguments to pass to the plugin\n ) -> Any: # Result of the plugin execution\n \"Execute a plugin's main functionality.\"\n \n def enable_plugin(\n self,\n plugin_name:str # Name of the plugin to enable\n ) -> bool: # True if plugin was enabled, False if not found\n \"Enable a plugin.\"\n \n def disable_plugin(\n self,\n plugin_name:str # Name of the plugin to disable\n ) -> bool: # True if plugin was disabled, False if not found\n \"Disable a plugin without unloading it.\"\n```\n\n### Plugin Metadata (`metadata.ipynb`)\n\n> Data structures for plugin metadata\n\n#### Import\n\n``` python\nfrom cjm_plugin_system.core.metadata import (\n PluginMeta\n)\n```\n\n#### Classes\n\n``` python\n@dataclass\nclass PluginMeta:\n \"Metadata about a plugin.\"\n \n name: str # Plugin's unique identifier\n version: str # Plugin's version string\n description: str = '' # Brief description of the plugin's functionality\n author: str = '' # Plugin author's name or organization\n package_name: str = '' # Python package name containing the plugin\n instance: Optional[Any] # Plugin instance (PluginInterface subclass)\n enabled: bool = True # Whether the plugin is enabled\n```\n\n### Configuration Validation (`validation.ipynb`)\n\n> JSON Schema validation helpers for plugin configuration\n\n#### Import\n\n``` python\nfrom cjm_plugin_system.utils.validation import (\n validate_config,\n extract_defaults\n)\n```\n\n#### Functions\n\n``` python\ndef validate_config(\n config:Dict[str, Any], # Configuration to validate\n schema:Dict[str, Any] # JSON Schema to validate against\n) -> Tuple[bool, Optional[str]]: # (is_valid, error_message)\n \"Validate a configuration dictionary against a JSON Schema.\"\n```\n\n``` python\ndef _basic_validate(\n config:Dict[str, Any], # Configuration to validate\n schema:Dict[str, Any] # JSON Schema to validate against\n) -> Tuple[bool, Optional[str]]: # (is_valid, error_message)\n \"Basic validation without jsonschema library.\"\n```\n\n``` python\ndef extract_defaults(\n schema:Dict[str, Any] # JSON Schema\n) -> Dict[str, Any]: # Default values from schema\n \"Extract default values from a JSON Schema.\"\n```\n",
"bugtrack_url": null,
"license": "Apache Software License 2.0",
"summary": "Generic plugin system with discovery, configuration validation, and runtime management for extensible Python applications.",
"version": "0.0.4",
"project_urls": {
"Homepage": "https://github.com/cj-mills/cjm-plugin-system"
},
"split_keywords": [
"nbdev",
"jupyter",
"notebook",
"python"
],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "5e7cdb65291bf8bbb9f3281f52da526220fa633c4731a3d3b3c22de7e3011afe",
"md5": "58ddb63dfe54d201337c51b3f3e89c1e",
"sha256": "759f77a115f3d6babb8d7d3df380122a1c137f07f5085fd787ca3c64371737b4"
},
"downloads": -1,
"filename": "cjm_plugin_system-0.0.4-py3-none-any.whl",
"has_sig": false,
"md5_digest": "58ddb63dfe54d201337c51b3f3e89c1e",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.9",
"size": 18164,
"upload_time": "2025-10-24T00:28:11",
"upload_time_iso_8601": "2025-10-24T00:28:11.494864Z",
"url": "https://files.pythonhosted.org/packages/5e/7c/db65291bf8bbb9f3281f52da526220fa633c4731a3d3b3c22de7e3011afe/cjm_plugin_system-0.0.4-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "9461b3eb7811edda5a13e7ba8bbc727d86780342c57c7ac761de0a5b2c054a36",
"md5": "ceec567394cd51f1fe159fce56d1e1bd",
"sha256": "376c2a8f5d8f59e583bf4d4a3d376a84d0a374cdfc1e92c4d96580414555d22b"
},
"downloads": -1,
"filename": "cjm_plugin_system-0.0.4.tar.gz",
"has_sig": false,
"md5_digest": "ceec567394cd51f1fe159fce56d1e1bd",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.9",
"size": 19278,
"upload_time": "2025-10-24T00:28:12",
"upload_time_iso_8601": "2025-10-24T00:28:12.528635Z",
"url": "https://files.pythonhosted.org/packages/94/61/b3eb7811edda5a13e7ba8bbc727d86780342c57c7ac761de0a5b2c054a36/cjm_plugin_system-0.0.4.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-10-24 00:28:12",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "cj-mills",
"github_project": "cjm-plugin-system",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"lcname": "cjm-plugin-system"
}