Skip to content

Commit 2b87fd1

Browse files
committed
full documentation
1 parent d07e30e commit 2b87fd1

7 files changed

Lines changed: 1268 additions & 151 deletions

File tree

README.md

Lines changed: 8 additions & 151 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,15 @@
66

77
All features are well documented and use a high level of `type_hints` for easy understanding and usage.
88

9-
## Features
9+
## Features
1010

11-
* `ConstantAttrib`: A descriptor that acts like a constant. Once set, the value cannot be changed. Raises `AttributeError` on change detection.
12-
* `RemoteAttrib`: A descriptor that acts as a remote attribute. You can modify the mapped value on-the-fly.
13-
* `ENVMod`: The main API class for managing `.env` variables. Supports manual and decorator-based registration of environment items, type-safe value loading, and `.env_example` generation.
14-
* `MethodMonitor`: A class to monitor method calls of a target class, triggering a handler function after the method is called.
15-
* `logwrap`: A dynamic decorator to log function calls. Uses the `logging` module with your current project configurations.
16-
* `suppress_errors`: A decorator that suppresses exceptions raised by the wrapped function and returns a fallback value instead.
11+
>(***Click names for more information***)
12+
* [`ConstantAttrib`](docs\constant_attrib.md): A descriptor that acts like a constant. Once set, the value cannot be changed. Raises `AttributeError` on change detection.
13+
* [`RemoteAttrib`](docs\remote_attrib.md): A descriptor that acts as a remote attribute. You can modify the mapped value on-the-fly.
14+
* [`ENVMod`](docs\env_mod.md): The main API class for managing `.env` variables. Supports manual and decorator-based registration of environment items, type-safe value loading, and `.env_example` generation.
15+
* [`MethodMonitor`](docs\method_monitor.md): A class to monitor method calls of a target class, triggering a handler function after the method is called.
16+
* [`logwrap`](docs\logwrap.md): A dynamic decorator to log function calls. Uses the `logging` module with your current project configurations.
17+
* [`suppress_errors`](docs\supress_errors.md): A decorator that suppresses exceptions raised by the wrapped function and returns a fallback value instead.
1718

1819
## Installation
1920

@@ -29,150 +30,6 @@ pip install classmods
2930
pip install git+https://github.com/hmohammad2520-org/classmods
3031
```
3132

32-
## Examples
33-
34-
### Constant Attribute
35-
36-
```python
37-
from classmods import ConstantAttrib
38-
39-
class Config:
40-
app_name = ConstantAttrib[str]()
41-
42-
def __init__(self, app_name):
43-
self.app_name = app_name
44-
45-
config = Config('new app')
46-
config.app_name = 'my app' # This will raise AttributeError
47-
```
48-
49-
### Remote Attribute
50-
51-
```python
52-
import requests
53-
from classmods import RemoteAttrib
54-
55-
class Config:
56-
token = RemoteAttrib[str](
57-
get=lambda: requests.get("https://api.example.com/auth").json()["token"],
58-
cache_timeout=10, # keeps result for 10 seconds
59-
)
60-
61-
config = Config()
62-
token = config.token # This will send a request and return the result
63-
```
64-
65-
### ENVMod
66-
67-
```python
68-
from os import PathLike
69-
from requests import Session
70-
from classmods import ENVMod
71-
72-
class Config:
73-
ENVMod.register(exclude=['session'], cast={'log_path': str})
74-
def __init__(
75-
self,
76-
app_name: str,
77-
session: Session, # Excluded non-parsable object
78-
log_path: PathLike,
79-
log_level: Optional[str] = None,
80-
port: int = 10,
81-
):
82-
'''
83-
Args:
84-
app_name (str): Application name.
85-
session (Session): Requests session.
86-
log_path (PathLike): Path of log file.
87-
log_level (Optional[str]): Log level, e.g., info.
88-
port (int): Session port, defaults to 10.
89-
'''
90-
91-
ENVMod.save_example('.my_example_path')
92-
ENVMod.load_dotenv('.my_env')
93-
ENVMod.sync_env_file('.my_env')
94-
```
95-
96-
#### Recommended (Type-safe, IDE-friendly) Argument Loading
97-
98-
```python
99-
config = Config(**ENVMod.load_args(Config.__init__), session=Session())
100-
```
101-
102-
**Why this is recommended:**
103-
104-
* Full IDE autocompletion
105-
* No static type checker errors
106-
* Explicit and predictable behavior
107-
* Best for libraries, frameworks, production code
108-
109-
---
110-
111-
#### Convenience Mode (Runtime Magic) Argument Loading
112-
113-
```python
114-
config = Config(envmod_loader, session=Session()) #type: ignore
115-
```
116-
117-
**What this does:**
118-
119-
* Automatically loads environment variables
120-
* Injects them into `__init__`
121-
* Overrides missing arguments
122-
123-
**Why `# type: ignore` is required:**
124-
125-
* Static analyzers cannot infer runtime argument injection
126-
* This is intentional and documented behavior
127-
128-
129-
### Method Monitor
130-
131-
```python
132-
from classmods import MethodMonitor
133-
134-
class MyClass:
135-
def my_method(self):
136-
pass
137-
138-
def my_handler(instance):
139-
print(f"Monitor triggered on {instance}")
140-
141-
monitor = MethodMonitor(MyClass, my_handler, target_method='my_method')
142-
obj = MyClass()
143-
obj.my_method()
144-
```
145-
146-
### logwrap
147-
148-
```python
149-
from classmods import logwrap
150-
151-
@logwrap(before=('INFO', '{func} starting, args={args} kwargs={kwargs}'), after=('INFO', '{func} ended'))
152-
def my_func(my_arg, my_kwarg=None):
153-
...
154-
155-
my_func('hello', my_kwarg=123) # Check logs to see the output
156-
```
157-
158-
### Suppress Errors
159-
160-
```python
161-
from classmods import suppress_errors
162-
163-
@suppress_errors(Exception)
164-
def risky_op() -> int:
165-
return 1 / 0
166-
167-
result = risky_op() # result = ZeroDivisionError
168-
169-
@suppress_errors(False)
170-
def safe_op() -> bool:
171-
raise ValueError("error")
172-
173-
result = safe_op() # result = False
174-
```
175-
17633
## License
17734

17835
MIT License

docs/constant_attrib.md

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
## `ConstantAttrib` Descriptor
2+
3+
### Overview
4+
5+
`ConstantAttrib` is a **data descriptor** that enforces **write-once (constant) attributes at the instance level**.
6+
7+
Once a value is assigned:
8+
9+
* It **cannot be reassigned**
10+
* It **cannot be deleted**
11+
* Access before assignment raises an error
12+
13+
This is useful when you want:
14+
15+
* Immutable instance configuration
16+
* Late initialization with safety
17+
* Explicit, self-documenting constants without `@property` boilerplate
18+
19+
---
20+
21+
## Key Characteristics
22+
23+
* Enforced **per instance**
24+
* Allows **exactly one assignment**
25+
* Read-only after initialization
26+
* Works cleanly with static type checkers (Pylance, MyPy)
27+
* Does **not** allow class-level assignment
28+
29+
---
30+
31+
## Basic Usage
32+
33+
```python
34+
class MyClass:
35+
VALUE = ConstantAttrib()
36+
37+
obj = MyClass()
38+
obj.VALUE = 42 # OK
39+
print(obj.VALUE) # 42
40+
41+
obj.VALUE = 10 # AttributeError
42+
del obj.VALUE # AttributeError
43+
```
44+
45+
---
46+
47+
## Behavior Summary
48+
49+
| Operation | Result |
50+
| ------------------------ | ------------------ |
51+
| First assignment | Allowed |
52+
| Second assignment |`AttributeError` |
53+
| Access before assignment |`AttributeError` |
54+
| Deletion |`AttributeError` |
55+
| Class-level access | Returns descriptor |
56+
57+
---
58+
59+
## Typing & Static Analysis
60+
61+
Because `ConstantAttrib` is generic:
62+
63+
```python
64+
class Config:
65+
PORT = ConstantAttrib[int]()
66+
```
67+
68+
Type checkers infer:
69+
70+
```python
71+
config.PORT # int
72+
```
73+
74+
This works without casts or `# type: ignore`.
75+
76+
---
77+
78+
## Advanced Example: Late Initialization
79+
80+
```python
81+
class Service:
82+
token = ConstantAttrib[str]()
83+
84+
def initialize(self, token: str) -> None:
85+
self.token = token
86+
```
87+
88+
```python
89+
svc = Service()
90+
svc.initialize("abc123")
91+
svc.token = "xyz" # AttributeError
92+
```
93+
94+
---
95+
96+
## Comparison With Alternatives
97+
98+
### vs `@property`
99+
100+
| Feature | ConstantAttrib | property |
101+
| ----------- | -------------- | ------------ |
102+
| Write-once | Yes | Manual |
103+
| Boilerplate | Minimal | High |
104+
| Typing | Excellent | Often tricky |
105+
| Storage | Automatic | Manual |
106+
107+
---
108+
109+
### vs `frozen=True` dataclass
110+
111+
| Feature | ConstantAttrib | Frozen dataclass |
112+
| --------------------- | -------------- | ---------------- |
113+
| Late init | Yes | No |
114+
| Partial immutability | Yes | No |
115+
| Per-attribute control | Yes | No |
116+
117+
---
118+
119+
## Design Philosophy
120+
121+
`ConstantAttrib` is:
122+
123+
* **Explicit**
124+
* **Predictable**
125+
* **Minimal**
126+
* **Safe for public APIs**
127+
128+
It avoids:
129+
130+
* Metaclasses
131+
* Magic mutation
132+
* Runtime patching
133+
* Silent behavior
134+
135+
This makes it ideal for **library code**, **configuration objects**, and **framework internals**.
136+
137+
---
138+
139+
## When to Use
140+
141+
Recommended for:
142+
143+
* Configuration values
144+
* Identifiers
145+
* Runtime-initialized constants
146+
* Public API invariants
147+
148+
Avoid using when:
149+
150+
* Values must change
151+
* Full immutability is required (use frozen dataclasses instead)

0 commit comments

Comments
 (0)