Python SDK for creating Excel XLL add-ins with xllify
Project description
xllify Python SDK
A Python SDK for creating high-performance Excel add-ins with xllify. Write Python functions and call them from Excel with automatic type conversion, error handling, and real-time updates.
Installation
You it is strongly recommended that you create a virtual environment and activate it.
pip install virtualenv # if you don't have it
virtualenv venv
source venv/bin/activate # mac OR
source venv\Scripts\activate # win
Then install xllify.
pip install xllify
xllify-install
Quick Start
1. Create your Python functions
# my_functions.py
import xllify
@xllify.fn("xllipy.Hello")
def hello(name: str = "World") -> str:
return f"Hello, {name}!"
@xllify.fn("xllipy.Add", category="Math")
def add(a: float, b: float) -> float:
return a + b
2. Build
xllify MyAddin.xll my_functions.py
3. Use in Excel
Functions are immediately available after you open the .xll in Excel.
=xllipy.Hello("World") -> "Hello, World!"
=xllipy.Add(5, 10) -> 15
Functions execute asynchronously - Excel shows #N/A while processing, then updates automatically when complete.
Workflow
Build and deployment
For production, Python files are embedded directly into your XLL:
xllify build MyAddin.xll main.py --requirements requirements.txt
When the XLL loads in Excel, Python files are extracted to %LOCALAPPDATA%\xllify\MyAddin\python\ and the Python process starts automatically.
Distribution
Just distribute the XLL file. Everything else (Python files, dependencies) is embedded and will be extracted automatically on first load.
Features
- Async by default - Functions run asynchronously so Excel never freezes
- Auto-reload - Hot reload functions during development without restarting Excel
- Matrix support - Return 2D arrays and pandas DataFrames directly to Excel
- Type-safe - Full type hints with
CellValue,Matrix, andExcelValue - Simple API - Just add the
@xllify.fn()decorator to your existing functions
Best practices
Our recommended approach is for you to implement your business logic in plain Python modules and call them from short functions wrapped with @xllify.fn. This keeps your core logic testable, reusable, and independent of Excel.
# my_logic.py - Pure Python, no xllify dependencies
def calculate_option_price(spot, strike, time, rate, volatility):
"""Black-Scholes call option pricing - testable business logic"""
# ... implementation ...
return price
# excel_functions.py - Thin xllify wrapper
import xllify
from my_logic import calculate_option_price
@xllify.fn("xllipy.BSCall", category="Finance")
def bs_call(s: float, k: float, t: float, r: float, sigma: float) -> float:
"""Excel wrapper for Black-Scholes calculation"""
return calculate_option_price(s, k, t, r, sigma)
Benefits:
- Testable: Run
pytestonmy_logic.pywithout Excel or xllify - Reusable: Use the same logic in web apps, CLIs, or other contexts without needing to import xllify
- Maintainable: Separate concerns between business logic and Excel integration
- Debuggable: Test and debug core logic independently
Advanced usage
Batching configuration
By default, xllify batches RTD updates for better performance (batch_size=500, batch_timeout_ms=50). You can customize this:
import xllify
# Configure batching before registering functions
xllify.configure_batching(
enabled=True,
batch_size=1000, # Batch up to 1000 updates together
batch_timeout_ms=100 # Wait up to 100ms before flushing
)
@xllify.fn("xllipy.Hello")
def hello(name: str) -> str:
return f"Hello, {name}!"
When to adjust batching:
- High-volume scenarios: Increase
batch_size(1000+) andbatch_timeout_ms(100+) for better throughput when handling many concurrent calculations - Low-latency requirements: Decrease
batch_timeout_ms(10-20ms) or disable batching entirely for faster individual responses - Balanced performance: Use defaults (batch_size=500, batch_timeout_ms=50)
Disable batching:
xllify.configure_batching(enabled=False) # Send updates immediately
Parameter metadata
Provide detailed parameter information for better documentation:
from xllify import fn, Parameter
@fn(
"xllipy.Calculate",
description="Perform calculation with optional delay",
category="Math",
parameters=[
Parameter("value", type="number", description="Value to process"),
Parameter("delay", type="number", description="Delay in seconds (optional)")
],
return_type="number"
)
def calculate(value: float, delay: float = 1.0) -> float:
"""Process a value with optional delay"""
import time
if delay > 0:
time.sleep(delay)
return value * 2
Working with arrays and matrices
Excel ranges are passed as 2D lists. You can also return matrices to Excel:
from xllify import Matrix
# Input: Accept ranges as 2D lists
@xllify.fn("xllipy.SumArray", description="Sum all numbers in a range")
def sum_array(numbers: list) -> float:
"""Sum a 2D array from Excel range"""
total = 0.0
for row in numbers:
for cell in row:
if isinstance(cell, (int, float)):
total += cell
return total
# Output: Return matrices to Excel
@xllify.fn("xllipy.GetData")
def get_data() -> Matrix:
"""Return a 2D array to Excel"""
return [
[1.0, True, "hello"],
[2.0, False, None], # None displays as empty cell
[3.0, None, "world"]
]
In Excel:
=xllipy.SumArray(A1:C10) -> Sum of all numbers
=xllipy.GetData() -> Spills 3x3 array into cells
Pandas DataFrames
Return DataFrames directly - automatically converted to Excel ranges with headers:
import pandas as pd
@xllify.fn("xllipy.GetDataFrame")
def get_dataframe() -> pd.DataFrame:
"""Return pandas DataFrame to Excel"""
return pd.DataFrame({
'Name': ['Alice', 'Bob', 'Charlie'],
'Age': [25, 30, 35],
'Score': [95.5, 87.3, 92.1]
})
In Excel, =xllipy.GetDataFrame() spills as:
Name Age Score
Alice 25 95.5
Bob 30 87.3
Charlie 35 92.1
Type mapping
| Excel Type | Python Type |
|---|---|
| Number | float |
| String | str |
| Boolean | bool |
| Range | List[List] (2D array) |
| Empty | None |
API reference
Type definitions
from xllify import CellValue, Matrix, ExcelValue
CellValue = Union[float, bool, str, None]
Matrix = List[List[CellValue]]
ExcelValue = Union[CellValue, Matrix, Any]
- CellValue: A single Excel cell value (number, boolean, string, or None for empty cells)
- Matrix: A 2D array of cell values
- ExcelValue: Any value that can be returned to Excel (scalar, matrix, or pandas DataFrame)
Decorators
@xllify.fn(name, description="", category="", parameters=None, return_type="")
Register a Python function as an Excel function.
Arguments:
name(str): Excel function name (e.g., "xllipy.MyFunc")description(str, optional): Function description (defaults to docstring)category(str, optional): Excel function categoryparameters(List[Parameter], optional): Parameter metadatareturn_type(str, optional): Return type override
Example:
@xllify.fn("xllipy.Add", description="Add numbers", category="Math")
def add(a: float, b: float) -> float:
return a + b
CLI commands
# Install the xllify tool and xllify-lua
xllify-install
# Clear async function cache
xllify-clear-cache
Error handling
Python exceptions are caught and returned to Excel as error strings:
@xllify.fn("xllipy.Divide")
def divide(a, b):
if b == 0:
raise ValueError("Division by zero")
return a / b
In Excel:
=xllipy.Divide(10, 0) -> #ERROR: Division by zero
More examples
Slow operations (async)
Functions run asynchronously - Excel never freezes:
@xllify.fn("xllipy.SlowCalc")
def slow_calc(seconds: float) -> str:
import time
time.sleep(seconds)
return f"Done after {seconds}s"
Excel shows #N/A while waiting, then updates automatically.
It is worth pointing out that this WILL however block your Python process. A simple workaround is to run multiple processes of the same Python script.
asynciosupport is planned.
Black-Scholes option pricing
from math import log, sqrt, exp, erf
@xllify.fn("xllipy.BSCall", category="Finance")
def black_scholes_call(s: float, k: float, t: float, r: float, sigma: float) -> float:
"""Black-Scholes call option price"""
if t <= 0:
return max(s - k, 0)
d1 = (log(s / k) + (r + 0.5 * sigma ** 2) * t) / (sigma * sqrt(t))
d2 = d1 - sigma * sqrt(t)
def norm_cdf(x):
return 0.5 * (1 + erf(x / sqrt(2)))
return s * norm_cdf(d1) - k * exp(-r * t) * norm_cdf(d2)
Usage: =xllipy.BSCall(100, 95, 0.25, 0.05, 0.2)
HTTP requests
@xllify.fn("xllipy.FetchPrice")
def fetch_price(symbol: str) -> float:
import requests
resp = requests.get(f"https://api.example.com/price/{symbol}")
return resp.json()["price"]
System info
@xllify.fn("xllipy.GetInfo")
def get_info() -> str:
import sys
import platform
return f"Python {sys.version.split()[0]} on {platform.system()}"
Development
Running tests
pip install -e ".[dev]"
pytest tests/ -v
Code formatting
black xllify/ tests/ examples/
License
MIT
Support
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file xllify-0.8.4.tar.gz.
File metadata
- Download URL: xllify-0.8.4.tar.gz
- Upload date:
- Size: 35.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
cc1c15aa48523e91da3bbaaac4ae69c28086afaaeae359b56bc3cd6827d0d5bb
|
|
| MD5 |
678f519f49d807fb35afeacb080e3865
|
|
| BLAKE2b-256 |
c7c78dbf5c88807e1d2533decfe1f9dc52cf07776d8322759960b155e4124057
|
File details
Details for the file xllify-0.8.4-py3-none-any.whl.
File metadata
- Download URL: xllify-0.8.4-py3-none-any.whl
- Upload date:
- Size: 32.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8d2cebdc35ee953eca35396bc22dd85840735e80cc79d70666a37274b7eb10bf
|
|
| MD5 |
e68f15ab4d5340f6731df788d94cafc9
|
|
| BLAKE2b-256 |
3c11e8a3014599ca0e5fcad2649a7381ca44230cdc624d5b2dc826c0ae130953
|