335 lines
8.9 KiB
Markdown
335 lines
8.9 KiB
Markdown
# `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: "<could not get function explanation: ...>"
|
|
```
|
|
|
|
---
|