# FynX
<p align="center">
<img src="https://github.com/off-by-some/fynx/raw/main/docs/images/banner.svg" alt="FynX Logo" style="border-radius: 16px; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12); max-width: 100%; height: auto;">
</p>
<p align="center">
<a href="#quick-start" style="text-decoration: none;">
<img src="https://raw.githubusercontent.com/off-by-some/fynx/main/docs/images/quick-start.svg" width="180" alt="Quick Start"/>
</a>
<a style="display: inline-block; width: 20px;"></a>
<a href="https://off-by-some.github.io/fynx/" style="text-decoration: none;">
<img src="https://raw.githubusercontent.com/off-by-some/fynx/main/docs/images/read-docs.svg" width="180" alt="Read the Docs"/>
</a>
<a style="display: inline-block; width: 20px;"></a>
<a href="https://github.com/off-by-some/fynx/blob/main/examples/" style="text-decoration: none;">
<img src="https://raw.githubusercontent.com/off-by-some/fynx/main/docs/images/code-examples.svg" width="180" alt="Examples"/>
</a>
<a style="display: inline-block; width: 20px;"></a>
<a href="https://github.com/off-by-some/fynx/issues" style="text-decoration: none;">
<img src="https://raw.githubusercontent.com/off-by-some/fynx/main/docs/images/get-support.svg" width="180" alt="Support"/>
</a>
</p>
<p align="center" style="margin-bottom: 0">
<a href="https://pypi.org/project/fynx/">
<img src="https://img.shields.io/pypi/v/fynx.svg?color=4169E1&label=PyPI" alt="PyPI Version">
</a>
<a href="https://github.com/off-by-some/fynx/actions/workflows/test.yml">
<img src="https://img.shields.io/github/actions/workflow/status/off-by-some/fynx/test.yml?branch=main&label=CI&color=2E8B57" alt="Build Status">
</a>
<a href="https://codecov.io/github/off-by-some/fynx" >
<img src="https://codecov.io/github/off-by-some/fynx/graph/badge.svg?token=NX2QHA8V8L"/>
</a>
<a href="https://opensource.org/licenses/MIT">
<img src="https://img.shields.io/badge/License-MIT-FF6B35.svg" alt="License: MIT">
</a>
<a href="https://off-by-some.github.io/fynx/">
<img src="https://img.shields.io/badge/docs-GitHub%20Pages-8A2BE2" alt="Documentation">
</a>
<a href="https://www.python.org/downloads/">
<img src="https://img.shields.io/pypi/pyversions/fynx.svg?label=Python&color=1E90FF" alt="Python Versions">
</a>
</p>
<p align="center" style=""><i>FynX ("Finks") = Functional Yielding Observable Networks</i></p>
**FynX** makes state management in Python feel inevitable rather than effortful. Inspired by [MobX](https://github.com/mobxjs/mobx) and functional reactive programming, the library turns your data reactive with minimal ceremony—declare relationships once, and updates cascade automatically through your entire application.
Whether you're building real-time [Streamlit](https://streamlit.io/) dashboards, data pipelines, or interactive applications, FynX ensures that when one value changes, everything depending on it updates instantly. No stale state. No forgotten dependencies. No manual synchronization.
**Define relationships once. Updates flow by necessity.**
## Quick Start
```bash
pip install fynx
```
```python
from fynx import Store, observable
class CartStore(Store):
item_count = observable(1)
price_per_item = observable(10.0)
# Define transformation function
def calculate_total(count, price):
return count * price
# Reactive computation using .then()
total_price = (CartStore.item_count + CartStore.price_per_item).then(calculate_total)
# total_price = (CartStore.item_count + CartStore.price_per_item) >> calculate_total # Equivalent!
def print_total(total):
print(f"Cart Total: ${total:.2f}")
total_price.subscribe(print_total)
# Automatic updates
CartStore.item_count = 3 # Cart Total: $30.00
CartStore.price_per_item = 12.50 # Cart Total: $37.50
```
This example captures the core promise: declare what should be true, and FynX ensures it remains true. For complete tutorials and patterns, see the [full documentation](https://off-by-some.github.io/fynx/) or explore [`examples/`](https://github.com/off-by-some/fynx/tree/main/examples/).
## Where FynX Applies
FynX works wherever values change over time and other computations depend on those changes. The reactive model scales naturally across domains:
* **Streamlit dashboards** with interdependent widgets ([see example](https://github.com/off-by-some/fynx/blob/main/examples/streamlit/todo_app.py))
* **Data pipelines** where downstream computations recalculate when upstream data arrives
* **Analytics systems** visualizing live, streaming data
* **Form validation** with complex interdependencies between fields
* **Real-time applications** where manual state coordination becomes unwieldy
* **ETL processes** with dynamic transformation chains
* **Monitoring systems** reacting to threshold crossings and composite conditions
* **Configuration systems** where derived settings update when base parameters change
The common thread: data flows through transformations, and multiple parts of your system need to stay synchronized. FynX handles the tedious work of tracking dependencies and triggering updates. You focus on *what* relationships should hold; the library ensures they do.
This breadth isn't accidental. The universal properties underlying FynX apply to any scenario involving time-varying values and compositional transformations—which describes a surprisingly large fraction of software.
## The Five Reactive Operators
FynX provides five composable operators that form a complete algebra for reactive programming. You can use either the symbolic operators (`>>`, `+`, `&`, `|`, `~`) or their natural language method equivalents (`.then()`, `.alongside()`, `.requiring()`, `.either()`, `.negate()`):
| Operator | Method | Operation | Purpose | Example |
|----------|--------|-----------|---------|---------|
| `>>` | `.then()` | Transform | Apply functions to values | `price >> (lambda p: f"${p:.2f}")` |
| `+` | `.alongside()` | Combine | Merge observables into read-only tuples | `(first + last) >> join` |
| `&` | `.requiring()` | Filter | Gate reactivity based on conditions | `file & valid & ~processing` |
| `\|` | `.either()` | Logical OR | Combine boolean conditions | `is_error \| is_warning` |
| `~` | `.negate()` | Negate | Invert boolean conditions | `~is_loading` |
Each operation creates a new observable. Chain them to build sophisticated reactive systems from simple parts. These operators correspond to precise mathematical structures—functors, products, pullbacks—that guarantee correct behavior under composition.
## The Mathematical Guarantee
You don't need to understand category theory to use FynX, but it's what makes FynX reliable: the reactive behavior isn't just validated by examples—it's guaranteed by mathematical necessity. Every reactive program you construct will work correctly because FynX is built on universal properties from category theory (detailed in the [**Mathematical Foundations**](https://off-by-some.github.io/fynx/generation/markdown/mathematical-foundations/)). These aren't abstract concepts for their own sake; they're implementation principles that ensure correctness and enable powerful optimizations.
FynX satisfies specific universal properties from category theory, guaranteeing correctness:
* **Functoriality**: Transformations with `>>` preserve composition. Your chains work exactly as expected, regardless of how you compose them.
* **Products**: Combining observables with `+` creates proper categorical products. No matter how complex your combinations, the structure stays coherent.
* **Pullbacks**: Filtering with `&` constructs mathematical pullbacks. Stack conditions freely—the meaning never changes.
The functoriality property guarantees that lifted functions preserve composition:
$$
\mathcal{O}(\mathrm{id}) = \mathrm{id} \quad \mathcal{O}(g \circ f) = \mathcal{O}g \circ \mathcal{O}f
$$
In practice, this means complex reactive systems composed from simple parts behave predictably under all transformations. You describe what relationships should exist; FynX guarantees they hold.
These same categorical structures enable FynX's automatic optimizer. Composition laws prove `obs >> f >> g >> h` can safely fuse into a single operation. Product properties allow sharing common computations. Pullback semantics let filters combine without changing meaning. The theory doesn't just ensure correctness—it shows exactly which optimizations are safe.
Think of it like an impossibly thorough test suite: one covering not just the cases you wrote, but every case that could theoretically exist. (We also ship with [conventional tests](./tests/), naturally.)
## Performance
While the theory guarantees correctness; implementation determines speed. FynX delivers both—and the mathematics directly enables the performance.
```bash
# Run the full benchmark suite
poetry install --with benchmark
poetry run python scripts/benchmark.py
```
Below is a sample of the output from the above command:
```
FynX Benchmark Configuration:
TIME_LIMIT_SECONDS: 1.0
STARTING_N: 10
SCALE_FACTOR: 1.5
╭───────────────────────── 🎯 FynX Benchmarks ────────────────────────────────────╮
│ FynX Performance Benchmark Suite │
╰─────────────────────────────────────────────────────────────────────────────────╯
Running Observable Creation benchmark...
✓ Observable Creation: 793,506 ops/sec (810325 items)
Running Individual Updates benchmark...
✓ Individual Updates: 352,902 ops/sec (360145 items)
Running Chain Propagation benchmark...
✓ Chain Propagation: 1,640 ops/sec (2776 links, 609μs latency)
Running Reactive Fan-out benchmark...
✓ Reactive Fan-out: 47,115 ops/sec (47427 items)
╭─────────────────────── 🎯 Real-World Performance Translation ───────────────────╮
│ ✓ Can handle ~47,427 UI components reacting to single state change │
│ ✓ Supports component trees up to 2,776 levels deep │
│ ✓ Processes 353K+ state updates per second │
│ ✓ Creates 794K+ observable objects per second │
│ ✓ Average propagation latency: 609μs per dependency link │
╰──────────────────────────────────────────────────────────────────────────────── ╯
```
The library processes over **353,000 state updates per second** and handles reactive graphs with **47,000+ dependent components** updating from a single source change using nothing but pure Python. This speed isn't accidental—it emerges from a categorical optimizer that rewrites your reactive graphs using proven algebraic transformations.
| Benchmark | Performance | Real-World Translation |
|-----------|-------------|------------------------|
| **Observable Creation** | 794K ops/sec | Create 790,000+ observable objects per second |
| **Individual Updates** | 353K ops/sec | Process 350,000+ state changes per second |
| **Chain Propagation** | 1.6K ops/sec | Support dependency chains 2,776 levels deep |
| **Reactive Fan-out** | 47K ops/sec | Update 47,000+ UI components from single state change |
Latency remains sub-microsecond for individual updates and averages 609μs per dependency link for complex chain propagation. This predictability matters—reactive systems shouldn't stutter when graphs grow large.
The optimizer details are covered in the [**Mathematical Foundations**](https://off-by-some.github.io/fynx/generation/markdown/mathematical-foundations/) documentation, which explains how FynX achieves this performance through a categorical graph optimizer that automatically applies proven rewrite rules based on functoriality, products, and pullbacks.
## Observables
[Observables](https://off-by-some.github.io/fynx/generation/markdown/observables/) form the foundation—reactive values that notify dependents automatically when they change. Create them standalone or organize them into [Stores](https://off-by-some.github.io/fynx/generation/markdown/stores/):
```python
from fynx import observable, Store
# Standalone observable
counter = observable(0)
counter.set(1) # Triggers reactive updates
# Store-based observables (recommended for organization)
class AppState(Store):
username = observable("")
is_logged_in = observable(False)
AppState.username = "off-by-some" # Normal assignment, reactive behavior
```
Stores provide structure for related state and enable features like store-level reactions and serialization. With observables established, you compose them using FynX's five fundamental operators.
## Transforming Data with `>>` or `.then()`
The `>>` operator (or `.then()` method) transforms observables through functions. Chain multiple transformations to build [derived observables](https://off-by-some.github.io/fynx/generation/markdown/derived-observables/):
```python
# Define transformation functions
def double(x):
return x * 2
def add_ten(x):
return x + 10
def format_result(x):
return f"Result: {x}"
# Chain transformations using .then()
result_method = (counter
.then(double)
.then(add_ten)
.then(format_result))
# Alternative syntax using >> operator
result_operator = (counter
>> double
>> add_ten
>> format_result)
```
Each transformation creates a new observable that recalculates when its source changes. This chaining works predictably because `>>` implements functorial mapping—structure preservation under transformation.
## Combining Observables with `+` or `.alongside()`
Use `+` (or `.alongside()`) to combine multiple observables into reactive tuples.
Merged observables are read-only computed observables that derive their value from their source observables:
```python
class User(Store):
first_name = observable("John")
last_name = observable("Doe")
# Define transformation function
def join_names(first, last):
return f"{first} {last}"
# Combine and transform using .then()
full_name_method = (User.first_name + User.last_name).then(join_names)
# Alternative using >> operator
full_name_operator = (User.first_name + User.last_name) >> join_names
# Merged observables are read-only
merged = User.first_name + User.last_name
# merged.set(("Jane", "Smith")) # Raises ValueError: Computed observables are read-only
```
When any combined observable changes, downstream values recalculate automatically. This operator constructs categorical products, ensuring combination remains symmetric and associative regardless of nesting.
## Filtering with `&`, `.requiring()`, `~`, `.negate()`, `|`, and `.either()`
The `&` operator (or `.requiring()`) filters observables to emit only when [conditions](https://off-by-some.github.io/fynx/generation/markdown/conditionals/) are met. Use `~` (or `.negate()`) to invert, and `|` (or `.either()`) for logical OR conditions:
```python
uploaded_file = observable(None)
is_processing = observable(False)
is_error = observable(False)
is_warning = observable(True)
# Define validation function
def is_valid_file(f):
return f is not None
# Conditional observables using .then()
is_valid_method = uploaded_file.then(is_valid_file)
is_valid_operator = uploaded_file >> is_valid_file
# Filter using & operator (or .requiring() method)
preview_ready_method = uploaded_file.requiring(is_valid_method).requiring(is_processing.negate())
preview_ready_operator = uploaded_file & is_valid_operator & (~is_processing)
# Logical OR using | operator (or .either() method)
needs_attention = is_error | is_warning
# Alternative: needs_attention = is_error.either(is_warning)
```
The `preview_ready` observable emits only when all conditions align—file exists, it's valid, and processing is inactive. The `needs_attention` observable emits when any error or warning condition is true. This filtering emerges from pullback constructions that create a "smart gate" filtering to the fiber where all conditions are True.
## Reacting to Changes
React to observable changes using the [`@reactive`](https://off-by-some.github.io/fynx/generation/markdown/using-reactive/) decorator or subscriptions.
**The fundamental principle**: `@reactive` is for side effects only—UI updates, logging, network calls, and other operations that interact with the outside world. For deriving new values from existing data, use the `>>` operator instead. This separation keeps your reactive system predictable and maintainable.
**Important note on timing**: Reactive functions don't fire immediately when created—they only fire when their dependencies *change*. This follows from FynX's pullback semantics in category theory. If you need initialization logic, handle it separately before setting up the reaction.
```python
from fynx import reactive
# GOOD: Side effects with @reactive
@reactive(user_count)
def update_dashboard(count):
render_ui(f"Users: {count}") # Side effect: UI update
@reactive(data_stream)
def sync_to_server(data):
api.post('/sync', data) # Side effect: network I/O
@reactive(error_log)
def log_errors(error):
print(f"Error: {error}") # Side effect: logging
# GOOD: Data transformations with >> operator
doubled = count >> (lambda x: x * 2) # Pure transformation
formatted = doubled >> (lambda x: f"${x:.2f}") # Pure transformation
# Inline subscriptions for dynamic behavior
observable.subscribe(lambda x: print(f"New value: {x}"))
# Conditional reactions using boolean operators
is_logged_in = observable(False)
has_data = observable(False)
is_loading = observable(True)
# React only when logged in AND has data AND NOT loading
@reactive(is_logged_in & has_data & ~is_loading)
def sync_when_ready(should_sync):
if should_sync:
perform_sync() # Side effect: network operation
# Multiple observables via derived state
first_name = observable("Alice")
last_name = observable("Smith")
# Derive first, then react
full_name = (first_name + last_name) >> (lambda f, l: f"{f} {l}")
@reactive(full_name)
def update_greeting(name):
display_message(f"Hello, {name}!") # Side effect: UI update
```
**Lifecycle management**: Use `.unsubscribe()` to stop reactive behavior when cleaning up components or changing modes. After unsubscribing, the function returns to normal, non-reactive behavior and can be called manually again.
```python
@reactive(data_stream)
def process_data(data):
handle_data(data)
# Later, during cleanup
process_data.unsubscribe() # Stops reacting to changes
```
**Remember**: Use `@reactive` for side effects at your application's boundaries—where your pure reactive data flow meets the outside world. Use `>>`, `+`, `&`, `|`, and `~` for all data transformations and computations. This "functional core, reactive shell" pattern is what makes reactive systems both powerful and maintainable.
## Additional Examples
Explore the [`examples/`](https://github.com/off-by-some/fynx/tree/main/examples/) directory for demonstrations across use cases:
| File | Description |
|------|-------------|
| [`basics.py`](https://github.com/off-by-some/fynx/blob/main/examples/basics.py) | Core concepts: observables, subscriptions, computed properties, stores, reactive decorators, conditional logic |
| [`cart_checkout.py`](https://github.com/off-by-some/fynx/blob/main/examples/cart_checkout.py) | Shopping cart with reactive total calculation |
| [`advanced_user_profile.py`](https://github.com/off-by-some/fynx/blob/main/examples/advanced_user_profile.py) | Complex reactive system with validation, notifications, persistence, and sophisticated computed properties |
| [`streamlit/store.py`](https://github.com/off-by-some/fynx/blob/main/examples/streamlit/store.py) | Custom StreamlitStore with automatic session state synchronization |
| [`streamlit/todo_app.py`](https://github.com/off-by-some/fynx/blob/main/examples/streamlit/todo_app.py) | Complete reactive todo list with Streamlit UI, real-time updates, and automatic persistence |
| [`streamlit/todo_store.py`](https://github.com/off-by-some/fynx/blob/main/examples/streamlit/todo_store.py) | Todo store with computed properties, filtering, and bulk operations |
These examples demonstrate how FynX's composable primitives scale from simple to sophisticated. The consistency across scales follows from the mathematical foundations.
## Design Philosophy
Deep mathematics should enable simpler code, not complicate it. FynX grounds itself in category theory precisely because those abstractions—functors, products, pullbacks—capture the essence of composition without the accidents of implementation. Users benefit from mathematical rigor whether they recognize the theory or not.
The interface reflects this. Observables feel like ordinary values—read them, write them, pass them around. Reactivity works behind the scenes, tracking dependencies through categorical structure without requiring explicit wiring. Method chaining flows naturally: `observable(42).subscribe(print)` reads as plain description, not ceremony. The `>>` operator transforms, `+` combines, `&` filters, `|` creates OR conditions, `~` negates—each produces new observables ready for further composition. Complex reactive systems emerge from simple, reusable pieces.
FynX offers multiple APIs because different contexts call for different styles. Use decorators when conciseness matters, direct calls when you need explicit control, context managers when reactions should be scoped. The library adapts to your preferred way of working.
The library remains framework agnostic by design. FynX has zero dependencies in its core and integrates cleanly with Streamlit, FastAPI, Flask, or any Python environment. Whether you're building web applications, data pipelines, or desktop software, the reactive primitives fit naturally without forcing architectural changes.
One current limitation: FynX operates single-threaded. Async support is planned as the concurrency model matures.
## Test Coverage
FynX maintains comprehensive test coverage tracked through Codecov:
| Sunburst Diagram | Grid Diagram | Icicle Diagram |
|---|---|---|
| <img src="https://codecov.io/github/off-by-some/fynx/graphs/sunburst.svg?token=NX2QHA8V8L" alt="Sunburst Coverage Diagram" height="200"/><br>*Inner circle represents the entire project, radiating outward through folders and files. Size and color indicate statement count and coverage.* | <img src="https://codecov.io/github/off-by-some/fynx/graphs/tree.svg?token=NX2QHA8V8L" alt="Grid Coverage Diagram" height="200"/><br>*Each block represents a file. Size and color indicate statement count and coverage.* | <img src="https://codecov.io/github/off-by-some/fynx/graphs/icicle.svg?token=NX2QHA8V8L" alt="Icicle Coverage Diagram" height="200"/><br>*Top section represents the entire project, with folders and files below. Size and color indicate statement count and coverage.* |
## Contributing
Contributions to FynX are welcome. This project uses **Poetry** for dependency management and **pytest** for testing.
> To learn more about the vision for version 1.0, see the [**1.0 Product Specification**](https://github.com/off-by-some/fynx/blob/main/docs/1.0_TODO.md).
### Getting Started
```bash
poetry install --with dev --with test
poetry run pre-commit install
poetry run pytest
```
Pre-commit hooks run automatically on each commit, checking code formatting and style. Run them manually across all files with `poetry run pre-commit run --all-files`.
### Development Workflow
* **Test your changes**: `poetry run pytest --cov=fynx`
* **Check linting**: `./scripts/lint.sh`
* **Auto-fix formatting**: `./scripts/lint.sh --fix`
* **Fork and create feature branch**: `feature/amazing-feature`
* **Add tests and ensure they pass**
* **Submit PR** with clear description of changes
<br>
## 🌟 Love FynX?
Support the evolution of reactive programming by [**starring the repository**](https://github.com/off-by-some/fynx) ⭐
***
<br>
<p align="center">
<strong>FynX</strong> — Functional Yielding Observable Networks
</p>
<p align="center">
<a href="https://github.com/off-by-some/fynx/blob/main/LICENSE">License</a> •
<a href="https://github.com/off-by-some/fynx/blob/main/CONTRIBUTING.md">Contributing</a> •
<a href="https://github.com/off-by-some/fynx/blob/main/CODE_OF_CONDUCT.md">Code of Conduct</a>
</p>
<p align="center">
<em>Crafted with ❤️ by <a href="https://github.com/off-by-some">Cassidy Bridges</a></em>
</p>
<p align="center">
© 2025 Cassidy Bridges • MIT Licensed
</p>
<br>
***
Raw data
{
"_id": null,
"home_page": "https://github.com/off-by-some/fynx",
"name": "fynx",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.9",
"maintainer_email": null,
"keywords": "reactive, state-management, observable, functional-programming, mobx",
"author": "Cassidy Bridges",
"author_email": "cassidybridges@gmail.com",
"download_url": "https://files.pythonhosted.org/packages/94/3e/73a8c60a6c5e52fe8fcf5f71214728258d3a8dba30e993da83d3beb86ab5/fynx-0.1.1.tar.gz",
"platform": null,
"description": "# FynX\n\n<p align=\"center\">\n <img src=\"https://github.com/off-by-some/fynx/raw/main/docs/images/banner.svg\" alt=\"FynX Logo\" style=\"border-radius: 16px; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12); max-width: 100%; height: auto;\">\n</p>\n\n<p align=\"center\">\n <a href=\"#quick-start\" style=\"text-decoration: none;\">\n <img src=\"https://raw.githubusercontent.com/off-by-some/fynx/main/docs/images/quick-start.svg\" width=\"180\" alt=\"Quick Start\"/>\n </a>\n <a style=\"display: inline-block; width: 20px;\"></a>\n <a href=\"https://off-by-some.github.io/fynx/\" style=\"text-decoration: none;\">\n <img src=\"https://raw.githubusercontent.com/off-by-some/fynx/main/docs/images/read-docs.svg\" width=\"180\" alt=\"Read the Docs\"/>\n </a>\n <a style=\"display: inline-block; width: 20px;\"></a>\n <a href=\"https://github.com/off-by-some/fynx/blob/main/examples/\" style=\"text-decoration: none;\">\n <img src=\"https://raw.githubusercontent.com/off-by-some/fynx/main/docs/images/code-examples.svg\" width=\"180\" alt=\"Examples\"/>\n </a>\n <a style=\"display: inline-block; width: 20px;\"></a>\n <a href=\"https://github.com/off-by-some/fynx/issues\" style=\"text-decoration: none;\">\n <img src=\"https://raw.githubusercontent.com/off-by-some/fynx/main/docs/images/get-support.svg\" width=\"180\" alt=\"Support\"/>\n </a>\n</p>\n\n<p align=\"center\" style=\"margin-bottom: 0\">\n <a href=\"https://pypi.org/project/fynx/\">\n <img src=\"https://img.shields.io/pypi/v/fynx.svg?color=4169E1&label=PyPI\" alt=\"PyPI Version\">\n </a>\n <a href=\"https://github.com/off-by-some/fynx/actions/workflows/test.yml\">\n <img src=\"https://img.shields.io/github/actions/workflow/status/off-by-some/fynx/test.yml?branch=main&label=CI&color=2E8B57\" alt=\"Build Status\">\n </a>\n <a href=\"https://codecov.io/github/off-by-some/fynx\" >\n <img src=\"https://codecov.io/github/off-by-some/fynx/graph/badge.svg?token=NX2QHA8V8L\"/>\n </a>\n <a href=\"https://opensource.org/licenses/MIT\">\n <img src=\"https://img.shields.io/badge/License-MIT-FF6B35.svg\" alt=\"License: MIT\">\n </a>\n <a href=\"https://off-by-some.github.io/fynx/\">\n <img src=\"https://img.shields.io/badge/docs-GitHub%20Pages-8A2BE2\" alt=\"Documentation\">\n </a>\n <a href=\"https://www.python.org/downloads/\">\n <img src=\"https://img.shields.io/pypi/pyversions/fynx.svg?label=Python&color=1E90FF\" alt=\"Python Versions\">\n </a>\n</p>\n\n<p align=\"center\" style=\"\"><i>FynX (\"Finks\") = Functional Yielding Observable Networks</i></p>\n\n**FynX** makes state management in Python feel inevitable rather than effortful. Inspired by [MobX](https://github.com/mobxjs/mobx) and functional reactive programming, the library turns your data reactive with minimal ceremony\u2014declare relationships once, and updates cascade automatically through your entire application.\n\nWhether you're building real-time [Streamlit](https://streamlit.io/) dashboards, data pipelines, or interactive applications, FynX ensures that when one value changes, everything depending on it updates instantly. No stale state. No forgotten dependencies. No manual synchronization.\n\n**Define relationships once. Updates flow by necessity.**\n\n## Quick Start\n\n```bash\npip install fynx\n```\n\n```python\nfrom fynx import Store, observable\n\nclass CartStore(Store):\n item_count = observable(1)\n price_per_item = observable(10.0)\n\n# Define transformation function\ndef calculate_total(count, price):\n return count * price\n\n# Reactive computation using .then()\ntotal_price = (CartStore.item_count + CartStore.price_per_item).then(calculate_total)\n# total_price = (CartStore.item_count + CartStore.price_per_item) >> calculate_total # Equivalent!\n\ndef print_total(total):\n print(f\"Cart Total: ${total:.2f}\")\n\ntotal_price.subscribe(print_total)\n\n# Automatic updates\nCartStore.item_count = 3 # Cart Total: $30.00\nCartStore.price_per_item = 12.50 # Cart Total: $37.50\n```\n\nThis example captures the core promise: declare what should be true, and FynX ensures it remains true. For complete tutorials and patterns, see the [full documentation](https://off-by-some.github.io/fynx/) or explore [`examples/`](https://github.com/off-by-some/fynx/tree/main/examples/).\n\n## Where FynX Applies\n\nFynX works wherever values change over time and other computations depend on those changes. The reactive model scales naturally across domains:\n\n* **Streamlit dashboards** with interdependent widgets ([see example](https://github.com/off-by-some/fynx/blob/main/examples/streamlit/todo_app.py))\n* **Data pipelines** where downstream computations recalculate when upstream data arrives\n* **Analytics systems** visualizing live, streaming data\n* **Form validation** with complex interdependencies between fields\n* **Real-time applications** where manual state coordination becomes unwieldy\n* **ETL processes** with dynamic transformation chains\n* **Monitoring systems** reacting to threshold crossings and composite conditions\n* **Configuration systems** where derived settings update when base parameters change\n\nThe common thread: data flows through transformations, and multiple parts of your system need to stay synchronized. FynX handles the tedious work of tracking dependencies and triggering updates. You focus on *what* relationships should hold; the library ensures they do.\n\nThis breadth isn't accidental. The universal properties underlying FynX apply to any scenario involving time-varying values and compositional transformations\u2014which describes a surprisingly large fraction of software.\n\n\n## The Five Reactive Operators\n\nFynX provides five composable operators that form a complete algebra for reactive programming. You can use either the symbolic operators (`>>`, `+`, `&`, `|`, `~`) or their natural language method equivalents (`.then()`, `.alongside()`, `.requiring()`, `.either()`, `.negate()`):\n\n| Operator | Method | Operation | Purpose | Example |\n|----------|--------|-----------|---------|---------|\n| `>>` | `.then()` | Transform | Apply functions to values | `price >> (lambda p: f\"${p:.2f}\")` |\n| `+` | `.alongside()` | Combine | Merge observables into read-only tuples | `(first + last) >> join` |\n| `&` | `.requiring()` | Filter | Gate reactivity based on conditions | `file & valid & ~processing` |\n| `\\|` | `.either()` | Logical OR | Combine boolean conditions | `is_error \\| is_warning` |\n| `~` | `.negate()` | Negate | Invert boolean conditions | `~is_loading` |\n\nEach operation creates a new observable. Chain them to build sophisticated reactive systems from simple parts. These operators correspond to precise mathematical structures\u2014functors, products, pullbacks\u2014that guarantee correct behavior under composition.\n\n## The Mathematical Guarantee\n\nYou don't need to understand category theory to use FynX, but it's what makes FynX reliable: the reactive behavior isn't just validated by examples\u2014it's guaranteed by mathematical necessity. Every reactive program you construct will work correctly because FynX is built on universal properties from category theory (detailed in the [**Mathematical Foundations**](https://off-by-some.github.io/fynx/generation/markdown/mathematical-foundations/)). These aren't abstract concepts for their own sake; they're implementation principles that ensure correctness and enable powerful optimizations.\n\nFynX satisfies specific universal properties from category theory, guaranteeing correctness:\n* **Functoriality**: Transformations with `>>` preserve composition. Your chains work exactly as expected, regardless of how you compose them.\n* **Products**: Combining observables with `+` creates proper categorical products. No matter how complex your combinations, the structure stays coherent.\n* **Pullbacks**: Filtering with `&` constructs mathematical pullbacks. Stack conditions freely\u2014the meaning never changes.\n\n\nThe functoriality property guarantees that lifted functions preserve composition:\n$$\n\\mathcal{O}(\\mathrm{id}) = \\mathrm{id} \\quad \\mathcal{O}(g \\circ f) = \\mathcal{O}g \\circ \\mathcal{O}f\n$$\n\nIn practice, this means complex reactive systems composed from simple parts behave predictably under all transformations. You describe what relationships should exist; FynX guarantees they hold.\n\nThese same categorical structures enable FynX's automatic optimizer. Composition laws prove `obs >> f >> g >> h` can safely fuse into a single operation. Product properties allow sharing common computations. Pullback semantics let filters combine without changing meaning. The theory doesn't just ensure correctness\u2014it shows exactly which optimizations are safe.\n\nThink of it like an impossibly thorough test suite: one covering not just the cases you wrote, but every case that could theoretically exist. (We also ship with [conventional tests](./tests/), naturally.)\n\n## Performance\n\nWhile the theory guarantees correctness; implementation determines speed. FynX delivers both\u2014and the mathematics directly enables the performance.\n\n```bash\n# Run the full benchmark suite\npoetry install --with benchmark\npoetry run python scripts/benchmark.py\n```\n\nBelow is a sample of the output from the above command:\n\n```\nFynX Benchmark Configuration:\n TIME_LIMIT_SECONDS: 1.0\n STARTING_N: 10\n SCALE_FACTOR: 1.5\n\n\u256d\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \ud83c\udfaf FynX Benchmarks \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256e\n\u2502 FynX Performance Benchmark Suite \u2502\n\u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256f\n\nRunning Observable Creation benchmark...\n\u2713 Observable Creation: 793,506 ops/sec (810325 items)\nRunning Individual Updates benchmark...\n\u2713 Individual Updates: 352,902 ops/sec (360145 items)\nRunning Chain Propagation benchmark...\n\u2713 Chain Propagation: 1,640 ops/sec (2776 links, 609\u03bcs latency)\nRunning Reactive Fan-out benchmark...\n\u2713 Reactive Fan-out: 47,115 ops/sec (47427 items)\n\n\u256d\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \ud83c\udfaf Real-World Performance Translation \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256e\n\u2502 \u2713 Can handle ~47,427 UI components reacting to single state change \u2502\n\u2502 \u2713 Supports component trees up to 2,776 levels deep \u2502\n\u2502 \u2713 Processes 353K+ state updates per second \u2502\n\u2502 \u2713 Creates 794K+ observable objects per second \u2502\n\u2502 \u2713 Average propagation latency: 609\u03bcs per dependency link \u2502\n\u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u256f\n```\n\nThe library processes over **353,000 state updates per second** and handles reactive graphs with **47,000+ dependent components** updating from a single source change using nothing but pure Python. This speed isn't accidental\u2014it emerges from a categorical optimizer that rewrites your reactive graphs using proven algebraic transformations.\n\n| Benchmark | Performance | Real-World Translation |\n|-----------|-------------|------------------------|\n| **Observable Creation** | 794K ops/sec | Create 790,000+ observable objects per second |\n| **Individual Updates** | 353K ops/sec | Process 350,000+ state changes per second |\n| **Chain Propagation** | 1.6K ops/sec | Support dependency chains 2,776 levels deep |\n| **Reactive Fan-out** | 47K ops/sec | Update 47,000+ UI components from single state change |\n\nLatency remains sub-microsecond for individual updates and averages 609\u03bcs per dependency link for complex chain propagation. This predictability matters\u2014reactive systems shouldn't stutter when graphs grow large.\n\nThe optimizer details are covered in the [**Mathematical Foundations**](https://off-by-some.github.io/fynx/generation/markdown/mathematical-foundations/) documentation, which explains how FynX achieves this performance through a categorical graph optimizer that automatically applies proven rewrite rules based on functoriality, products, and pullbacks.\n\n## Observables\n\n[Observables](https://off-by-some.github.io/fynx/generation/markdown/observables/) form the foundation\u2014reactive values that notify dependents automatically when they change. Create them standalone or organize them into [Stores](https://off-by-some.github.io/fynx/generation/markdown/stores/):\n\n```python\nfrom fynx import observable, Store\n\n# Standalone observable\ncounter = observable(0)\ncounter.set(1) # Triggers reactive updates\n\n# Store-based observables (recommended for organization)\nclass AppState(Store):\n username = observable(\"\")\n is_logged_in = observable(False)\n\nAppState.username = \"off-by-some\" # Normal assignment, reactive behavior\n```\n\nStores provide structure for related state and enable features like store-level reactions and serialization. With observables established, you compose them using FynX's five fundamental operators.\n\n\n## Transforming Data with `>>` or `.then()`\n\nThe `>>` operator (or `.then()` method) transforms observables through functions. Chain multiple transformations to build [derived observables](https://off-by-some.github.io/fynx/generation/markdown/derived-observables/):\n\n```python\n# Define transformation functions\ndef double(x):\n return x * 2\n\ndef add_ten(x):\n return x + 10\n\ndef format_result(x):\n return f\"Result: {x}\"\n\n# Chain transformations using .then()\nresult_method = (counter\n .then(double)\n .then(add_ten)\n .then(format_result))\n\n# Alternative syntax using >> operator\nresult_operator = (counter\n >> double\n >> add_ten\n >> format_result)\n```\n\nEach transformation creates a new observable that recalculates when its source changes. This chaining works predictably because `>>` implements functorial mapping\u2014structure preservation under transformation.\n\n## Combining Observables with `+` or `.alongside()`\n\nUse `+` (or `.alongside()`) to combine multiple observables into reactive tuples.\nMerged observables are read-only computed observables that derive their value from their source observables:\n\n```python\nclass User(Store):\n first_name = observable(\"John\")\n last_name = observable(\"Doe\")\n\n# Define transformation function\ndef join_names(first, last):\n return f\"{first} {last}\"\n\n# Combine and transform using .then()\nfull_name_method = (User.first_name + User.last_name).then(join_names)\n\n# Alternative using >> operator\nfull_name_operator = (User.first_name + User.last_name) >> join_names\n\n# Merged observables are read-only\nmerged = User.first_name + User.last_name\n# merged.set((\"Jane\", \"Smith\")) # Raises ValueError: Computed observables are read-only\n```\n\nWhen any combined observable changes, downstream values recalculate automatically. This operator constructs categorical products, ensuring combination remains symmetric and associative regardless of nesting.\n\n## Filtering with `&`, `.requiring()`, `~`, `.negate()`, `|`, and `.either()`\n\nThe `&` operator (or `.requiring()`) filters observables to emit only when [conditions](https://off-by-some.github.io/fynx/generation/markdown/conditionals/) are met. Use `~` (or `.negate()`) to invert, and `|` (or `.either()`) for logical OR conditions:\n\n```python\nuploaded_file = observable(None)\nis_processing = observable(False)\nis_error = observable(False)\nis_warning = observable(True)\n\n# Define validation function\ndef is_valid_file(f):\n return f is not None\n\n# Conditional observables using .then()\nis_valid_method = uploaded_file.then(is_valid_file)\nis_valid_operator = uploaded_file >> is_valid_file\n\n# Filter using & operator (or .requiring() method)\npreview_ready_method = uploaded_file.requiring(is_valid_method).requiring(is_processing.negate())\npreview_ready_operator = uploaded_file & is_valid_operator & (~is_processing)\n\n# Logical OR using | operator (or .either() method)\nneeds_attention = is_error | is_warning\n# Alternative: needs_attention = is_error.either(is_warning)\n```\n\nThe `preview_ready` observable emits only when all conditions align\u2014file exists, it's valid, and processing is inactive. The `needs_attention` observable emits when any error or warning condition is true. This filtering emerges from pullback constructions that create a \"smart gate\" filtering to the fiber where all conditions are True.\n\n## Reacting to Changes\n\nReact to observable changes using the [`@reactive`](https://off-by-some.github.io/fynx/generation/markdown/using-reactive/) decorator or subscriptions.\n\n**The fundamental principle**: `@reactive` is for side effects only\u2014UI updates, logging, network calls, and other operations that interact with the outside world. For deriving new values from existing data, use the `>>` operator instead. This separation keeps your reactive system predictable and maintainable.\n\n**Important note on timing**: Reactive functions don't fire immediately when created\u2014they only fire when their dependencies *change*. This follows from FynX's pullback semantics in category theory. If you need initialization logic, handle it separately before setting up the reaction.\n\n```python\nfrom fynx import reactive\n\n# GOOD: Side effects with @reactive\n@reactive(user_count)\ndef update_dashboard(count):\n render_ui(f\"Users: {count}\") # Side effect: UI update\n\n@reactive(data_stream)\ndef sync_to_server(data):\n api.post('/sync', data) # Side effect: network I/O\n\n@reactive(error_log)\ndef log_errors(error):\n print(f\"Error: {error}\") # Side effect: logging\n\n# GOOD: Data transformations with >> operator\ndoubled = count >> (lambda x: x * 2) # Pure transformation\nformatted = doubled >> (lambda x: f\"${x:.2f}\") # Pure transformation\n\n# Inline subscriptions for dynamic behavior\nobservable.subscribe(lambda x: print(f\"New value: {x}\"))\n\n# Conditional reactions using boolean operators\nis_logged_in = observable(False)\nhas_data = observable(False)\nis_loading = observable(True)\n\n# React only when logged in AND has data AND NOT loading\n@reactive(is_logged_in & has_data & ~is_loading)\ndef sync_when_ready(should_sync):\n if should_sync:\n perform_sync() # Side effect: network operation\n\n# Multiple observables via derived state\nfirst_name = observable(\"Alice\")\nlast_name = observable(\"Smith\")\n\n# Derive first, then react\nfull_name = (first_name + last_name) >> (lambda f, l: f\"{f} {l}\")\n\n@reactive(full_name)\ndef update_greeting(name):\n display_message(f\"Hello, {name}!\") # Side effect: UI update\n```\n\n**Lifecycle management**: Use `.unsubscribe()` to stop reactive behavior when cleaning up components or changing modes. After unsubscribing, the function returns to normal, non-reactive behavior and can be called manually again.\n\n```python\n@reactive(data_stream)\ndef process_data(data):\n handle_data(data)\n\n# Later, during cleanup\nprocess_data.unsubscribe() # Stops reacting to changes\n```\n\n**Remember**: Use `@reactive` for side effects at your application's boundaries\u2014where your pure reactive data flow meets the outside world. Use `>>`, `+`, `&`, `|`, and `~` for all data transformations and computations. This \"functional core, reactive shell\" pattern is what makes reactive systems both powerful and maintainable.\n\n## Additional Examples\n\nExplore the [`examples/`](https://github.com/off-by-some/fynx/tree/main/examples/) directory for demonstrations across use cases:\n\n| File | Description |\n|------|-------------|\n| [`basics.py`](https://github.com/off-by-some/fynx/blob/main/examples/basics.py) | Core concepts: observables, subscriptions, computed properties, stores, reactive decorators, conditional logic |\n| [`cart_checkout.py`](https://github.com/off-by-some/fynx/blob/main/examples/cart_checkout.py) | Shopping cart with reactive total calculation |\n| [`advanced_user_profile.py`](https://github.com/off-by-some/fynx/blob/main/examples/advanced_user_profile.py) | Complex reactive system with validation, notifications, persistence, and sophisticated computed properties |\n| [`streamlit/store.py`](https://github.com/off-by-some/fynx/blob/main/examples/streamlit/store.py) | Custom StreamlitStore with automatic session state synchronization |\n| [`streamlit/todo_app.py`](https://github.com/off-by-some/fynx/blob/main/examples/streamlit/todo_app.py) | Complete reactive todo list with Streamlit UI, real-time updates, and automatic persistence |\n| [`streamlit/todo_store.py`](https://github.com/off-by-some/fynx/blob/main/examples/streamlit/todo_store.py) | Todo store with computed properties, filtering, and bulk operations |\n\nThese examples demonstrate how FynX's composable primitives scale from simple to sophisticated. The consistency across scales follows from the mathematical foundations.\n\n## Design Philosophy\n\nDeep mathematics should enable simpler code, not complicate it. FynX grounds itself in category theory precisely because those abstractions\u2014functors, products, pullbacks\u2014capture the essence of composition without the accidents of implementation. Users benefit from mathematical rigor whether they recognize the theory or not.\n\nThe interface reflects this. Observables feel like ordinary values\u2014read them, write them, pass them around. Reactivity works behind the scenes, tracking dependencies through categorical structure without requiring explicit wiring. Method chaining flows naturally: `observable(42).subscribe(print)` reads as plain description, not ceremony. The `>>` operator transforms, `+` combines, `&` filters, `|` creates OR conditions, `~` negates\u2014each produces new observables ready for further composition. Complex reactive systems emerge from simple, reusable pieces.\n\nFynX offers multiple APIs because different contexts call for different styles. Use decorators when conciseness matters, direct calls when you need explicit control, context managers when reactions should be scoped. The library adapts to your preferred way of working.\n\nThe library remains framework agnostic by design. FynX has zero dependencies in its core and integrates cleanly with Streamlit, FastAPI, Flask, or any Python environment. Whether you're building web applications, data pipelines, or desktop software, the reactive primitives fit naturally without forcing architectural changes.\n\nOne current limitation: FynX operates single-threaded. Async support is planned as the concurrency model matures.\n\n## Test Coverage\n\nFynX maintains comprehensive test coverage tracked through Codecov:\n\n| Sunburst Diagram | Grid Diagram | Icicle Diagram |\n|---|---|---|\n| <img src=\"https://codecov.io/github/off-by-some/fynx/graphs/sunburst.svg?token=NX2QHA8V8L\" alt=\"Sunburst Coverage Diagram\" height=\"200\"/><br>*Inner circle represents the entire project, radiating outward through folders and files. Size and color indicate statement count and coverage.* | <img src=\"https://codecov.io/github/off-by-some/fynx/graphs/tree.svg?token=NX2QHA8V8L\" alt=\"Grid Coverage Diagram\" height=\"200\"/><br>*Each block represents a file. Size and color indicate statement count and coverage.* | <img src=\"https://codecov.io/github/off-by-some/fynx/graphs/icicle.svg?token=NX2QHA8V8L\" alt=\"Icicle Coverage Diagram\" height=\"200\"/><br>*Top section represents the entire project, with folders and files below. Size and color indicate statement count and coverage.* |\n\n## Contributing\n\nContributions to FynX are welcome. This project uses **Poetry** for dependency management and **pytest** for testing.\n\n> To learn more about the vision for version 1.0, see the [**1.0 Product Specification**](https://github.com/off-by-some/fynx/blob/main/docs/1.0_TODO.md).\n\n### Getting Started\n\n```bash\npoetry install --with dev --with test\npoetry run pre-commit install\npoetry run pytest\n```\n\nPre-commit hooks run automatically on each commit, checking code formatting and style. Run them manually across all files with `poetry run pre-commit run --all-files`.\n\n### Development Workflow\n\n* **Test your changes**: `poetry run pytest --cov=fynx`\n* **Check linting**: `./scripts/lint.sh`\n* **Auto-fix formatting**: `./scripts/lint.sh --fix`\n* **Fork and create feature branch**: `feature/amazing-feature`\n* **Add tests and ensure they pass**\n* **Submit PR** with clear description of changes\n\n<br>\n\n## \ud83c\udf1f Love FynX?\n\nSupport the evolution of reactive programming by [**starring the repository**](https://github.com/off-by-some/fynx) \u2b50\n\n***\n\n<br>\n\n<p align=\"center\">\n <strong>FynX</strong> \u2014 Functional Yielding Observable Networks\n</p>\n\n<p align=\"center\">\n <a href=\"https://github.com/off-by-some/fynx/blob/main/LICENSE\">License</a> \u2022\n <a href=\"https://github.com/off-by-some/fynx/blob/main/CONTRIBUTING.md\">Contributing</a> \u2022\n <a href=\"https://github.com/off-by-some/fynx/blob/main/CODE_OF_CONDUCT.md\">Code of Conduct</a>\n</p>\n\n<p align=\"center\">\n <em>Crafted with \u2764\ufe0f by <a href=\"https://github.com/off-by-some\">Cassidy Bridges</a></em>\n</p>\n\n<p align=\"center\">\n \u00a9 2025 Cassidy Bridges \u2022 MIT Licensed\n</p>\n\n<br>\n\n***\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "Python reactive state management library inspired by MobX",
"version": "0.1.1",
"project_urls": {
"Homepage": "https://github.com/off-by-some/fynx",
"Repository": "https://github.com/off-by-some/fynx"
},
"split_keywords": [
"reactive",
" state-management",
" observable",
" functional-programming",
" mobx"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "ea74ded95f5d59a4f51283e4bbafb06c1f8fdcc37f27bb57990b495bb04481e0",
"md5": "376966cd16e42252a86fa2fbf1d359fc",
"sha256": "b20ad3cbd6c26999c60e8042199cab1c774907663eec3e2133ca13f82e015fea"
},
"downloads": -1,
"filename": "fynx-0.1.1-py3-none-any.whl",
"has_sig": false,
"md5_digest": "376966cd16e42252a86fa2fbf1d359fc",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.9",
"size": 84454,
"upload_time": "2025-10-21T02:06:41",
"upload_time_iso_8601": "2025-10-21T02:06:41.951948Z",
"url": "https://files.pythonhosted.org/packages/ea/74/ded95f5d59a4f51283e4bbafb06c1f8fdcc37f27bb57990b495bb04481e0/fynx-0.1.1-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "943e73a8c60a6c5e52fe8fcf5f71214728258d3a8dba30e993da83d3beb86ab5",
"md5": "732008cbaf644f48c3a31aac33483737",
"sha256": "54d6dc952f0eb42ceca1651a7851e34fef79fffa55bcf35d774974f5df14fd02"
},
"downloads": -1,
"filename": "fynx-0.1.1.tar.gz",
"has_sig": false,
"md5_digest": "732008cbaf644f48c3a31aac33483737",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.9",
"size": 80853,
"upload_time": "2025-10-21T02:06:43",
"upload_time_iso_8601": "2025-10-21T02:06:43.689692Z",
"url": "https://files.pythonhosted.org/packages/94/3e/73a8c60a6c5e52fe8fcf5f71214728258d3a8dba30e993da83d3beb86ab5/fynx-0.1.1.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-10-21 02:06:43",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "off-by-some",
"github_project": "fynx",
"travis_ci": false,
"coveralls": true,
"github_actions": true,
"lcname": "fynx"
}