Contributing¶
Contributing to shortfx¶
Thank you for your interest in contributing to shortfx! This guide explains how to add new formulas to the project step by step.
Table of Contents¶
- Project Architecture
- Adding a New Function to an Existing Module
- Creating a New File in an Existing Module
- Creating a New Module
- Function Design Rules
- Docstring Format
- Input Validation
- Writing Tests
- Updating Documentation
- MCP Tool Name Convention
- Checklist Before Submitting
Project Architecture¶
How It Works¶
- Each
fx*module has an__init__.pythat callsauto_export()from_loader.py. auto_export()dynamically imports submodules and re-exports all their public callables.registry.pywalks allfx*packages at runtime to discover functions and build JSON schemas.- The MCP server uses
registry.pyto expose functions via meta-tools — no manual registration needed.
Key takeaway: you only need to write a function in the right file and register the file in __init__.py. Everything else (discovery, MCP exposure, schema generation) is automatic.
Adding a New Function to an Existing Module¶
This is the simplest case. Example: adding a cube_root function to fxNumeric/arithmetic_functions.py.
Step 1 — Write the function¶
Open the target file and add your function:
# shortfx/fxNumeric/arithmetic_functions.py
def cube_root(x: float) -> float:
"""Calculates the cube root of a number.
Description:
Computes x^(1/3), handling negative numbers correctly.
Args:
x: The number to compute the cube root for.
Returns:
The cube root of x.
Raises:
TypeError: If x is not numeric.
Usage Example:
>>> from shortfx.fxNumeric.arithmetic_functions import cube_root
>>> cube_root(27)
3.0
>>> cube_root(-8)
-2.0
Cost: O(1)
"""
if not isinstance(x, (int, float)):
raise TypeError("x must be numeric (int or float).")
if x >= 0:
return x ** (1 / 3)
return -((-x) ** (1 / 3))
Step 2 — No additional registration needed¶
Since arithmetic_functions is already listed in fxNumeric/__init__.py, auto_export picks up any new public function automatically.
Step 3 — Write tests¶
Add tests to the corresponding test file (see Writing Tests).
Step 4 — Update llms.txt¶
Add an entry under the correct ### file.py section (see Updating Documentation).
Creating a New File in an Existing Module¶
Example: adding a matrix_functions.py file to fxNumeric.
Step 1 — Create the file¶
# shortfx/fxNumeric/matrix_functions.py
"""Matrix operations module.
This module provides basic matrix operations such as transposition,
multiplication, determinant, and identity generation.
"""
from typing import List
def matrix_transpose(matrix: List[List[float]]) -> List[List[float]]:
"""Transposes a 2D matrix (swaps rows and columns).
Description:
Given an m×n matrix, returns an n×m matrix where element [i][j]
becomes [j][i].
Args:
matrix: A 2D list representing the matrix.
Returns:
The transposed matrix.
Raises:
TypeError: If matrix is not a list of lists.
ValueError: If rows have inconsistent lengths.
Usage Example:
>>> from shortfx.fxNumeric.matrix_functions import matrix_transpose
>>> matrix_transpose([[1, 2], [3, 4]])
[[1, 3], [2, 4]]
Cost: O(m·n)
"""
if not isinstance(matrix, list) or not all(isinstance(r, list) for r in matrix):
raise TypeError("matrix must be a list of lists.")
if matrix and len(set(len(r) for r in matrix)) > 1:
raise ValueError("All rows must have the same length.")
return [list(row) for row in zip(*matrix)]
Step 2 — Register the file in __init__.py¶
Open shortfx/fxNumeric/__init__.py and add the new file to _SUBMODULES:
_SUBMODULES = [
"arithmetic_functions",
"calculator_functions",
"constants_functions",
"conversion_functions",
"distribution_functions",
"finance_functions",
"format_functions",
"interpolation_functions",
"matrix_functions", # ← NEW
"number_theory_functions",
"random_functions",
"rounding_functions",
"statistics_functions",
"trigonometry_functions",
]
Step 3 — Write tests and update docs¶
Follow Writing Tests and Updating Documentation.
Creating a New Module¶
If none of the existing modules (fxDate, fxNumeric, fxString, fxPython, fxExcel, fxVBA) fits your functions, you can create a new module.
Step 1 — Create the module directory¶
Step 2 — Write __init__.py¶
Follow the exact same pattern as existing modules:
# shortfx/fxGeometry/__init__.py
"""fxGeometry — Geometric calculations and shape operations.
Re-exports all public functions from submodules. Submodules with
uninstalled optional dependencies are silently skipped.
"""
from shortfx._loader import auto_export
_SUBMODULES = [
"shape_functions",
]
auto_export("shortfx.fxGeometry", _SUBMODULES, globals())
Step 3 — Write your function files¶
Follow the same structure as Adding a New Function.
Step 4 — Update documentation¶
- Add a new
## fxGeometrysection inllms.txt. - Update the function count in the header of
llms.txt. - Add the module to the
README.mdmain modules list. - Create a
README.mdinsideshortfx/fxGeometry/describing the module.
Function Design Rules¶
Every function in shortfx must follow these principles:
| Rule | Description |
|---|---|
| Atomic | One function = one operation. Split into independent steps if possible. |
| Pure | Same inputs → same output. No side effects. |
| Composable | Small building blocks that combine easily via chaining or nesting. |
| No hidden dependencies | All inputs via parameters, all outputs via return values. |
| Type-annotated | Always annotate parameters and return types. |
| Explicit failures | Raise specific exceptions (TypeError, ValueError), never return ambiguous sentinels. |
| Naming reflects action | Name describes what it returns or does: calculate_total, is_valid_date, parse_row. |
| Pythonic | Clear, concise, minimal lines. |
Docstring Format¶
Use Google-style docstrings with all these sections:
def function_name(param: type) -> return_type:
"""Brief one-line description.
Description:
More detailed description (if needed) on as few lines as possible.
Args:
param: Purpose of the parameter.
Returns:
Description of the return value.
Raises:
ExceptionType: When it occurs.
Usage Example:
>>> from shortfx.fxModule.file import function_name
>>> function_name(value)
expected_result
Cost: O(n)
"""
Required sections: description line, Args, Returns. Include Raises if the function raises exceptions. Always include a Usage Example and Cost (Big-O complexity).
Input Validation¶
Use the shared helpers in shortfx/_validators.py for consistent error messages:
from shortfx._validators import ensure_type, ensure_numeric, ensure_positive
def my_function(value: float, name: str) -> float:
ensure_numeric(value, "value")
ensure_positive(value, "value")
ensure_type(name, str, "name")
# ... logic
Available validators:
| Function | Purpose |
|---|---|
ensure_type(value, expected, name) |
Checks isinstance |
ensure_numeric(value, name) |
Checks int or float |
ensure_positive(value, name) |
Checks > 0 |
For domain-specific validation (e.g., date ranges), validate inline with clear error messages.
Writing Tests¶
Tests live in the tests/ directory and use pytest.
Naming convention¶
- File:
test_<descriptive_name>.py - Function:
test_<unit>_<scenario>
Example test file¶
# tests/test_matrix_functions.py
"""Tests for fxNumeric matrix operations."""
import pytest
from shortfx.fxNumeric.matrix_functions import matrix_transpose
def test_matrix_transpose_square():
assert matrix_transpose([[1, 2], [3, 4]]) == [[1, 3], [2, 4]]
def test_matrix_transpose_rectangular():
assert matrix_transpose([[1, 2, 3], [4, 5, 6]]) == [[1, 4], [2, 5], [3, 6]]
def test_matrix_transpose_empty():
assert matrix_transpose([]) == []
def test_matrix_transpose_invalid_type():
with pytest.raises(TypeError):
matrix_transpose("not a matrix")
def test_matrix_transpose_inconsistent_rows():
with pytest.raises(ValueError):
matrix_transpose([[1, 2], [3]])
@pytest.mark.parametrize("matrix, expected", [
([[1]], [[1]]),
([[1, 2]], [[1], [2]]),
])
def test_matrix_transpose_parametrized(matrix, expected):
assert matrix_transpose(matrix) == expected
Running tests¶
uv run pytest # All tests
uv run pytest tests/test_matrix_functions.py # Specific file
uv run pytest -k "transpose" # Filter by keyword
Updating Documentation¶
Every new function requires updates to two files in the same step:
1. llms.txt¶
Add the function entry under the correct ### file.py section:
### matrix_functions.py
- `matrix_transpose(matrix)` - Transposes a 2D matrix (swaps rows and columns)
If you created a new file, add a new ### file.py heading under the correct module section. Update the total function count in the header:
2. Module README.md¶
If the module has a README.md (e.g., shortfx/fxNumeric/README.md), add the new function there.
MCP Tool Name Convention¶
Functions are automatically exposed via the MCP server with this naming pattern:
Example:
fxNumeric.matrix_functions.matrix_transpose
─────────┬──────────────── ────────────────
│ │ └── Function name
│ └── Source file (without .py)
└── Module name
You do not need to manually register the function in the MCP server — registry.py discovers it automatically from the fx* packages.
Checklist Before Submitting¶
Use this checklist to make sure your contribution is complete:
- [ ] Function written in the correct module/file
- [ ] Type hints on all parameters and return type
- [ ] Google-style docstring with Description, Args, Returns, Raises, Usage Example, Cost
- [ ] Input validation using
_validators.pyhelpers or inline checks - [ ] Pure function — no side effects, no hidden dependencies
- [ ] File registered in
__init__.py_SUBMODULES(if new file) - [ ] Tests written in
tests/withpytest - [ ]
llms.txtupdated with function entry and updated count - [ ] Module README updated (if applicable)
- [ ] All tests pass —
uv run pytest
Quick Reference: Where to Put Your Function¶
| Function Type | Module | Example Files |
|---|---|---|
| Date/time operations | fxDate |
date_operations.py, date_convertions.py |
| Math, arithmetic, calculus | fxNumeric |
arithmetic_functions.py, rounding_functions.py |
| Finance, interest, amortization | fxNumeric |
finance_functions.py |
| Statistics, distributions | fxNumeric |
statistics_functions.py, distribution_functions.py |
| Text manipulation, formatting | fxString |
string_operations.py, string_similarity.py |
| Collections, itertools, logic | fxPython |
py_itertools.py, py_convertions.py |
| Excel-compatible formulas | fxExcel |
math_formulas.py, lookup_formulas.py |
| VBA/Access-compatible | fxVBA |
array_functions.py, financial_functions.py |