A Python package for financial modeling and reporting
Project description
pyproforma
A Python library for building financial models — a code-first alternative to Excel for pro formas, projections, and structured financial tables.
pip install pyproforma
Why
Spreadsheets are the default tool for financial modeling, but they have real problems: no version control, no testing, formulas hidden inside cells, and no easy way to generate the same model for multiple scenarios. pyproforma is designed for analysts who want the benefits of code — reproducibility, testability, version history — without giving up the tabular output that finance people actually use.
How it works
Define a model by subclassing ProformaModel and declaring line items as class attributes. Instantiate it with a list of periods and the library calculates everything.
from pyproforma import ProformaModel, FixedLine, FormulaLine, Assumption
class IncomeStatement(ProformaModel):
growth_rate = Assumption(value=0.10)
revenue = FixedLine(
values={2024: 500_000, 2025: 550_000, 2026: 605_000},
label="Revenue",
tags=["operating"],
)
cogs = FormulaLine(
formula=lambda li, t: li.revenue[t] * 0.55,
label="Cost of Goods Sold",
tags=["operating"],
)
gross_profit = FormulaLine(
formula=lambda li, t: li.revenue[t] - li.cogs[t],
label="Gross Profit",
)
tax_expense = FormulaLine(
formula=lambda li, t: li.gross_profit[t] * 0.21,
label="Tax Expense",
)
net_income = FormulaLine(
formula=lambda li, t: li.gross_profit[t] - li.tax_expense[t],
label="Net Income",
)
model = IncomeStatement(periods=[2024, 2025, 2026])
Access results directly:
model["net_income"][2025] # 218_130.0
model["gross_profit"][2024] # 225_000.0
Tables
Generate formatted tables for display or export. Tables are the primary output — they render to HTML (for Jupyter notebooks), Excel, or a pandas DataFrame.
# All line items
model.tables.line_items().show()
# Single item with period-over-period analysis
model.tables.line_item("net_income", include_percent_change=True).show()
# Show what feeds into a calculated line item
model.tables.precedents("net_income").show()
# Custom layout using a template
from pyproforma import Format
from pyproforma.tables import HeaderRow, LabelRow, ItemRow
table = model.tables.from_template([
HeaderRow(background_color="lightblue"),
LabelRow(label="Income Statement"),
ItemRow(name="revenue", value_format=Format.THOUSANDS_K),
ItemRow(name="cogs", value_format=Format.THOUSANDS_K, bottom_border="single"),
ItemRow(name="gross_profit", bold=True, value_format=Format.THOUSANDS_K),
])
table.show()
Export to Excel with formatting preserved:
model.tables.line_items().to_excel("income_statement.xlsx")
Scenarios and comparisons
A model is just a class — create multiple instances with different inputs to compare scenarios.
class FlexModel(ProformaModel):
cogs_rate = Assumption(value=0.55)
revenue = FixedLine(values={2024: 500_000, 2025: 550_000})
cogs = FormulaLine(formula=lambda li, t: li.revenue[t] * li.cogs_rate)
gross_profit = FormulaLine(formula=lambda li, t: li.revenue[t] - li.cogs[t])
base = FlexModel(periods=[2024, 2025])
# InputAssumption lets callers override at instantiation time — coming soon
Use ModelComparison to diff two model instances:
from pyproforma import ModelComparison
# (requires two separately instantiated models with different inputs)
comparison = ModelComparison(base, upside)
comparison.diff("gross_profit")
Time-series formulas
Formulas receive the full model namespace and the current period t. Reference prior periods with t-1:
# Compound growth from a seeded base year
revenue = FormulaLine(
formula=lambda li, t: li.revenue[t-1] * (1 + li.growth_rate),
values={2024: 500_000}, # seed value for first period
label="Revenue",
)
Tags
Tag line items to group them flexibly without fixed categories:
revenue = FixedLine(values={...}, tags=["operating", "top_line"])
other_income = FixedLine(values={...}, tags=["operating"])
# Sum all items tagged "operating" in a formula
ebit = FormulaLine(formula=lambda li, t: li.tag["operating"][t])
# Or in a table
from pyproforma.tables import TagTotalRow
table = model.tables.from_template([
HeaderRow(),
ItemRow(name="revenue"),
ItemRow(name="other_income"),
TagTotalRow(tag="operating", label="Total Operating"),
])
Number formatting
A Format class provides named format constants that flow through to both HTML and Excel output:
from pyproforma import Format
ItemRow(name="revenue", value_format=Format.THOUSANDS_K) # 500K
ItemRow(name="margin", value_format=Format.PERCENT_ONE_DECIMAL) # 45.0%
ItemRow(name="net_income", value_format=Format.CURRENCY_NO_DECIMALS) # $218,130
Custom formats via NumberFormatSpec:
from pyproforma import NumberFormatSpec
fmt = NumberFormatSpec(decimals=1, scale="millions", suffix="M")
# 500_000 → "0.5M"
Installation
pip install pyproforma
Requires Python 3.9+. Dependencies: pandas, openpyxl.
Status
This project is in active development. The core modeling and table export features are stable. Charts, extended documentation, and additional row types are on the roadmap. Feedback welcome — open an issue or reach out directly.
License
MIT
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 pyproforma-0.2.0.tar.gz.
File metadata
- Download URL: pyproforma-0.2.0.tar.gz
- Upload date:
- Size: 55.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2324fb4c287a31a9ce9859d99f84c821064b524b1aa8c126ee0287e03a6fc2a2
|
|
| MD5 |
0554108c19255cf75fe3b5f124e7736d
|
|
| BLAKE2b-256 |
4d911bc43ec59a96e35d7d04946caa7a4a904d72ee4e7a021e684cf461a9c127
|
File details
Details for the file pyproforma-0.2.0-py3-none-any.whl.
File metadata
- Download URL: pyproforma-0.2.0-py3-none-any.whl
- Upload date:
- Size: 66.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bf635aed0a0fef871152a941df4a205ad89fad125bd3acc55437e4b4f7d76f4b
|
|
| MD5 |
c8ad9e2a8b6f1287bc61e96cd25d016c
|
|
| BLAKE2b-256 |
ebd29bba8f7bd611f876f05f37fc68d1da46f741d056db62704738e3a2e69900
|