Skip to content

Commit 1d59a6e

Browse files
updated docstrings
1 parent ff4f6f9 commit 1d59a6e

File tree

6 files changed

+131
-39
lines changed

6 files changed

+131
-39
lines changed

src/python_project_template/__init__.py

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,18 @@
1-
"""python-project-template: mini math utility library
1+
"""Top-level package for python_project_template.
22
3-
Exports:
4-
- Operation, Operation registry, Calculator, convenience operations
3+
This package exposes the core types and a small set of convenience symbols
4+
for the mini calculator demo project.
5+
6+
Module layout (concise):
7+
- exceptions: error classes
8+
- operations: Operation class and example operations
9+
- registry: OperationRegistry for registering operations
10+
- calculator: Calculator class to apply/compose operations
11+
- utils: tiny helper functions
12+
13+
The exported symbols are intentionally small and stable for tests and
14+
examples. Use :func:`default_calculator` to quickly get a Calculator pre-
15+
populated with common operations.
516
"""
617

718
from .exceptions import CalculatorError, OperationError, RegistryError
@@ -26,12 +37,18 @@
2637
"is_number",
2738
]
2839

29-
# Provide a default registry populated with common ops
40+
41+
# Default registry pre-populated with a few convenience operations.
3042
_default_registry = OperationRegistry()
3143
for op in (ADD, SUB, MUL, DIV, NEG, SQR):
3244
_default_registry.register(op)
3345

3446

35-
# Convenience Calculator constructor using default registry
3647
def default_calculator() -> Calculator:
48+
"""Create a Calculator using the package default registry.
49+
50+
Returns:
51+
Calculator: a calculator with common operations already registered.
52+
"""
53+
3754
return Calculator(registry=_default_registry)

src/python_project_template/calculator.py

Lines changed: 37 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,13 @@
1-
"""Calculator that can register and compose operations."""
1+
"""Calculator utilities for applying and composing operations.
2+
3+
The :class:`Calculator` provides a thin layer over :class:`OperationRegistry`.
4+
It supports applying named operations, composing unary operations into a
5+
callable, and a small 'chain' helper for mixed sequences of unary/binary
6+
operations used by the examples and tests.
7+
8+
Keep the behaviour minimal: methods raise :class:`OperationError` for
9+
operation-related failures.
10+
"""
211

312
from typing import Callable, Iterable, List, Any, Optional
413

@@ -8,13 +17,13 @@
817

918

1019
class Calculator:
11-
"""A tiny calculator that uses an OperationRegistry.
20+
"""Thin calculator wrapper around an OperationRegistry.
1221
13-
Features:
14-
15-
- register operations (via registry or helper)
16-
- apply a named operation to arguments
17-
- compose a sequence of operations into a single callable
22+
Methods:
23+
register(op, replace=False): Register an Operation.
24+
apply(op_name, *args): Apply a registered operation.
25+
compose(ops, left_to_right=True): Compose unary operations.
26+
chain(sequence, initial): Apply a mixed sequence DSL-style.
1827
"""
1928

2029
def __init__(self, registry: Optional[OperationRegistry] = None):
@@ -24,9 +33,22 @@ def __init__(self, registry: Optional[OperationRegistry] = None):
2433
self.registry = registry
2534

2635
def register(self, op: Operation, *, replace: bool = False) -> None:
36+
"""Register an :class:`Operation` with the calculator's registry.
37+
38+
Args:
39+
op: Operation to register.
40+
replace: If True, replace an existing operation with the same name.
41+
"""
42+
2743
self.registry.register(op, replace=replace)
2844

2945
def apply(self, op_name: str, *args: Any) -> Any:
46+
"""Apply a named operation to the provided arguments.
47+
48+
Raises:
49+
OperationError: wraps underlying errors from the operation call.
50+
"""
51+
3052
op = self.registry.get(op_name)
3153
try:
3254
return op(*args)
@@ -38,17 +60,11 @@ def apply(self, op_name: str, *args: Any) -> Any:
3860
def compose(
3961
self, ops: Iterable[str], *, left_to_right: bool = True
4062
) -> Callable[[Any], Any]:
41-
"""Compose a sequence of unary operations into a single callable.
42-
43-
The composed function takes one argument and applies the operations in order.
44-
Only unary operations (arity == 1) are supported for composition.
45-
46-
Args:
47-
ops: iterable of operation names
48-
left_to_right: if True, apply first op then next (f2(f1(x))).
63+
"""Compose a sequence of unary operations into a callable.
4964
50-
Returns:
51-
callable f(x)
65+
Only supports unary operations (arity == 1). The returned function
66+
accepts a single value and applies the operations in the requested
67+
order.
5268
"""
5369

5470
op_list: List[Operation] = [self.registry.get(name) for name in ops]
@@ -77,16 +93,11 @@ def composed(x):
7793
def chain(self, sequence: Iterable[str], initial: Any) -> Any:
7894
"""Apply a mixed sequence of operations to an initial value.
7995
80-
For binary operations, the operation consumes (current_value, next_input)
81-
and returns a new current_value. To support binary ops in a chain, the
82-
sequence should alternate between operation names and provided literals
83-
(which are interpreted as inputs). Example::
84-
85-
sequence = ['add', 5, 'sqr']
86-
chain(sequence, initial=2) -> sqr(add(2,5)) = (2+5)^2
87-
88-
This is a very small DSL useful for demos.
96+
The sequence may contain operation names (str) and literal values.
97+
Binary operations consume the current value and the next literal.
98+
This helper is intentionally small and tailored for tests/examples.
8999
"""
100+
90101
seq = list(sequence)
91102
cur = initial
92103
i = 0

src/python_project_template/exceptions.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,21 @@
1-
"""Custom exceptions for the mini calculator library."""
1+
"""Custom exceptions used across the mini calculator package.
2+
3+
All exceptions inherit from :class:`CalculatorError` so callers may catch the
4+
base class for broad error handling. Use the more specific subclasses for
5+
programmatic checks in tests or higher-level code.
6+
"""
27

38

49
class CalculatorError(Exception):
5-
"""Base class for calculator-related errors."""
10+
"""Base class for calculator-related errors.
11+
12+
Use this as the top-level exception when handling errors coming from the
13+
package.
14+
"""
615

716

817
class OperationError(CalculatorError):
9-
"""Raised when an operation fails (e.g. wrong arity or invalid input)."""
18+
"""Raised when an operation fails (wrong arity, invalid input, etc.)."""
1019

1120

1221
class RegistryError(CalculatorError):

src/python_project_template/operations.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
"""Definition of Operation and a handful of example math operations."""
1+
"""Operation container and a few example math operations.
2+
3+
The :class:`Operation` is a tiny callable wrapper that validates arity and
4+
delegates to the underlying function. A small set of convenience
5+
instances (ADD, SUB, MUL, DIV, NEG, SQR) are provided for tests/examples.
6+
"""
27

38
from dataclasses import dataclass
49
from typing import Callable, Any
@@ -8,6 +13,14 @@
813

914
@dataclass
1015
class Operation:
16+
"""Container for a named callable with an expected arity.
17+
18+
Attributes:
19+
name: operation name used for registry lookups.
20+
func: callable implementing the operation.
21+
arity: number of positional arguments expected by the operation.
22+
"""
23+
1124
name: str
1225
func: Callable[..., Any]
1326
arity: int = 1
@@ -24,29 +37,41 @@ def __call__(self, *args):
2437

2538

2639
def add(a, b):
40+
"""Return the sum of two numbers."""
41+
2742
return a + b
2843

2944

3045
def sub(a, b):
46+
"""Return the difference of two numbers."""
47+
3148
return a - b
3249

3350

3451
def mul(a, b):
52+
"""Return the product of two numbers."""
53+
3554
return a * b
3655

3756

3857
def safe_div(a, b):
58+
"""Divide a by b, raising :class:`OperationError` on zero division."""
59+
3960
try:
4061
return a / b
4162
except ZeroDivisionError as e:
4263
raise OperationError("Division by zero") from e
4364

4465

4566
def neg(a):
67+
"""Return the numeric negation of a value."""
68+
4669
return -a
4770

4871

4972
def square(a):
73+
"""Return the square of a value."""
74+
5075
return a * a
5176

5277

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
"""Registry for operations: register, lookup, decorator support."""
1+
"""Operation registry used to register and lookup operations by name.
2+
3+
The :class:`OperationRegistry` is intentionally minimal: register operations,
4+
retrieve them by name, list registered names, and update from another
5+
registry. It raises :class:`RegistryError` for lookup/registration problems.
6+
"""
27

38
from typing import Dict, Iterable
49

@@ -9,30 +14,46 @@
914
class OperationRegistry:
1015
"""A simple name->Operation registry.
1116
12-
Usage:
17+
Example:
1318
reg = OperationRegistry()
14-
reg.register(Operation(...))
19+
reg.register(Operation('add', func, arity=2))
1520
op = reg.get('add')
1621
"""
1722

1823
def __init__(self):
1924
self._ops: Dict[str, Operation] = {}
2025

2126
def register(self, op: Operation, *, replace: bool = False) -> None:
27+
"""Register an :class:`Operation`.
28+
29+
Args:
30+
op: operation instance to register.
31+
replace: if False (default) raise on duplicate names.
32+
"""
33+
2234
if op.name in self._ops and not replace:
2335
raise RegistryError(f"Operation already registered: {op.name}")
2436
self._ops[op.name] = op
2537

2638
def get(self, name: str) -> Operation:
39+
"""Return a registered Operation by name.
40+
41+
Raises:
42+
RegistryError: if the name is unknown.
43+
"""
44+
2745
try:
2846
return self._ops[name]
2947
except KeyError:
3048
raise RegistryError(f"Unknown operation: {name}")
3149

3250
def list_ops(self) -> Iterable[str]:
51+
"""Return a list of registered operation names."""
52+
3353
return list(self._ops.keys())
3454

3555
def update(self, other: "OperationRegistry") -> None:
3656
"""Update the registry with operations from another registry."""
57+
3758
for name in other.list_ops():
3859
self._ops[name] = other.get(name)
Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,16 @@
1-
"""Small utilities for the mini calculator library."""
1+
"""Tiny helper utilities used by the examples and tests.
2+
3+
Only lightweight predicates live here to keep the package dependency-free and
4+
easy to read.
5+
"""
26

37
from typing import Any
48

59

610
def is_number(x: Any) -> bool:
11+
"""Return True if ``x`` is an int or float.
12+
13+
Useful in examples and tests to guard numeric operations.
14+
"""
15+
716
return isinstance(x, (int, float))

0 commit comments

Comments
 (0)