# `multinut` ![PyPI - Version](https://img.shields.io/pypi/v/multinut) ![Downloads - Monthly](https://static.pepy.tech/personalized-badge/multinut?period=month&units=international_system&left_color=black&right_color=orange) ![Python Versions](https://img.shields.io/pypi/pyversions/multinut) ![Wheel](https://img.shields.io/pypi/wheel/multinut) The multitool nobody asked for. Includes stuff and so. --- ## `multinut.env` A simple but flexible environment loader. Supports `.env` file parsing with optional mode suffixes (e.g. `.env.production`, `.env.testing`, etc.), lazy loading, and dynamic access. ### Features * Mode-based config support (`.env.production`, `.env.testing`, etc.) * Access via: * `env["KEY"]` * `env.get("KEY", ...)` * `env.KEY` * Optional type casting (`str`, `bool`, `list`, `dict`, etc.) * Sane default handling * Does **not** mutate `os.environ` --- ### Use cases * Loading `.env` files in mode-aware Python projects * Separating secrets and configs by deployment context * Dynamically reading values like `env.DB_URL`, `env.get("DEBUG", default=False, cast=cast_bool)` * Avoiding `os.environ` pollution --- ### Basic Example ```python from multinut.env import Environment, Modes env = Environment(env_file_name=".env", mode=Modes.DEVELOPMENT) print(env.get("DEBUG", default=False)) print(env["API_KEY"]) print(env.DB_URL) ``` Given a `.env.development` file: ```env DEBUG=true API_KEY=secret DB_URL=https://example.com ``` --- ### Smart Casts Example ```python from multinut.env import ( Environment, cast_bool, cast_int, cast_float, cast_list, cast_tuple, cast_dict, cast_none_or_str ) env = Environment() print("INT:", env.get("PORT", cast=cast_int)) # -> int print("FLOAT:", env.get("PI", cast=cast_float)) # -> float print("BOOL:", env.get("ENABLED", cast=cast_bool)) # -> bool print("LIST:", env.get("NUMBERS", cast=cast_list)) # -> list[str] print("TUPLE:", env.get("WORDS", cast=cast_tuple)) # -> tuple[str] print("DICT:", env.get("CONFIG", cast=cast_dict)) # -> dict print("NONE_OR_STR:", env.get("OPTIONAL", cast=cast_none_or_str)) # -> None or str ``` Example `.env`: ```env PORT=8080 PI=3.1415 ENABLED=yes NUMBERS=1,2,3 WORDS=hello,world,test CONFIG={"timeout": 30, "debug": true} OPTIONAL=null ``` ### Included Cast Helpers All built-in cast functions handle common edge cases: | Cast Function | Description | | ------------------ | ------------------------------------------- | | `cast_str` | Ensures string | | `cast_int` | Converts to integer | | `cast_float` | Converts to float | | `cast_bool` | Accepts `1`, `true`, `yes`, `on`, etc. | | `cast_list` | Comma-split list | | `cast_tuple` | Comma-split, converted to tuple | | `cast_dict` | Parses JSON string into dictionary | | `cast_none_or_str` | Returns `None` if value is `null` or `None` | --- ### EnviromentKeyMissing if a key is get from `env.get` and it has no default given it will raise EnviromentKeyMissing(f"Environment variable '{key}' not found.") --- ## `multinut.funky` A type-aware method overloading system for Python classes. Provides a decorator-based approach to method overloading that dispatches based on argument types, allowing multiple implementations of the same method with different type signatures. ### Features * Type-based method dispatch using Python type annotations * Support for both exact type matching (`type(value) is ann`) and inheritance-based matching (`isinstance(value, ann)`) * Automatic signature binding and validation * Clean decorator syntax with `@overload` * Descriptor protocol support for seamless integration with class methods --- ### Use cases * Creating polymorphic methods that behave differently based on argument types * Building APIs with type-specific implementations * Implementing mathematical operations that work with different numeric types * Creating flexible data processing methods that handle various input formats --- ### Basic Example ```python from multinut.funky import Dispatcher, overload class Calculator: add = Dispatcher("add") @overload(add) def add(self, x: int, y: int) -> int: return x + y @overload(add) def add(self, x: str, y: str) -> str: return x + y @overload(add) def add(self, x: list, y: list) -> list: return x + y calc = Calculator() print(calc.add(1, 2)) # -> 3 (int) print(calc.add("a", "b")) # -> "ab" (str) print(calc.add([1], [2])) # -> [1, 2] (list) ``` --- ### Complex Type Dispatch Example ```python from multinut.funky import Dispatcher, overload from typing import Union class DataProcessor: process = Dispatcher("process") @overload(process) def process(self, data: str) -> str: return f"Processing string: {data.upper()}" @overload(process) def process(self, data: int) -> str: return f"Processing number: {data * 2}" @overload(process) def process(self, data: list) -> str: return f"Processing list of {len(data)} items" @overload(process) def process(self, data: dict) -> str: return f"Processing dict with keys: {list(data.keys())}" processor = DataProcessor() print(processor.process("hello")) # -> "Processing string: HELLO" print(processor.process(42)) # -> "Processing number: 84" print(processor.process([1, 2, 3])) # -> "Processing list of 3 items" print(processor.process({"a": 1, "b": 2})) # -> "Processing dict with keys: ['a', 'b']" ``` ### Error Handling When no matching overload is found, a `TypeError` is raised with details about the failed dispatch: ```python # This will raise: TypeError: No matching overload for add(1.5, 2.5) calc.add(1.5, 2.5) # No overload for float arguments ``` --- ## `multinut.explain` An AI-powered code explanation system that adds `.explain()` methods to classes and functions. Provides automatic code documentation by leveraging OpenAI's GPT models to generate human-readable explanations of Python code. Classes can inherit from `Explainable` to automatically gain explanation capabilities. ### Features * Automatic `.explain()` method injection for classes and their methods * AI-powered code analysis using OpenAI's GPT-4 * Environment-based API key management via `.env` files * Source code introspection and intelligent explanation generation * Method-level and class-level explanations * Graceful error handling for missing API keys or source code issues --- ### Use cases * Automatically generating documentation for complex classes * Understanding legacy code or third-party implementations * Creating educational materials and code walkthroughs * Quick code review and comprehension assistance * Onboarding new developers with self-documenting code --- ### Basic Example ```python from multinut.explain import Explainable # Set up API key from environment Explainable.use_env(".env") # Looks for OPENAPI_KEY in .env file class Calculator(Explainable): def add(self, x: int, y: int) -> int: return x + y def multiply(self, x: int, y: int) -> int: return x * y # Explain the entire class print(Calculator.explain()) # Explain individual methods calc = Calculator() print(calc.add.explain()) print(calc.multiply.explain()) ``` ### Environment Setup Create a `.env` file with your OpenAI API key: ```env OPENAPI_KEY=your-openai-api-key-here ``` Or set the API key directly: ```python from multinut.explain import Explainable Explainable.API_KEY = "your-openai-api-key-here" ``` --- ### Complex Example ```python from multinut.explain import Explainable class DataAnalyzer(Explainable): def __init__(self, dataset): self.dataset = dataset self.processed_data = [] def clean_data(self, remove_nulls=True): cleaned = [item for item in self.dataset if item is not None] if remove_nulls: cleaned = [item for item in cleaned if item != ""] return cleaned def calculate_stats(self, data): if not data: return {"mean": 0, "count": 0} return { "mean": sum(data) / len(data), "count": len(data), "max": max(data), "min": min(data) } # Get AI explanations analyzer = DataAnalyzer([1, 2, 3, None, 4]) print("Class explanation:") print(DataAnalyzer.explain()) print("\nMethod explanation:") print(analyzer.clean_data.explain()) ``` ### Error Handling The system handles various error conditions gracefully: ```python # Missing API key try: Calculator.explain() except ApiKeyMissingError as e: print(f"Error: {e}") # Invalid source code or API issues print(some_method.explain()) # Returns: "" ``` ---