inventra
Multi-channel inventory sync for small and mid-market eCommerce — real-time conflict resolution, async sync engine, drift detection, and audit log.
Sync inventory across Shopify, Amazon, eBay, WooCommerce, and custom channels with automatic conflict resolution. No $500/mo SaaS required.
The Problem
$1 trillion/year is lost to stockouts. 34% of small merchants report real-time sync as their top operational headache. Enterprise tools start at $500/mo — nothing affordable exists for independent sellers.
Installation
pip install inventra
Quick Start
from inventra import InventorySync, InventoryItem, ConflictResolution
from inventra.channels.mock import MockChannelAdapter
# Set up two channels with different stock levels
shopify = MockChannelAdapter(config={}, initial_inventory={"SKU-001": 50, "SKU-002": 10})
shopify.channel_name = "shopify"
shopify.connect()
amazon = MockChannelAdapter(config={}, initial_inventory={"SKU-001": 45, "SKU-002": 10})
amazon.channel_name = "amazon"
amazon.connect()
# Sync with lowest-stock wins (prevents overselling)
sync = InventorySync(
channels=[shopify, amazon],
conflict_resolution=ConflictResolution.LOWEST_STOCK,
)
report = sync.sync()
print(report.summary())
# {'total': 2, 'success': 2, 'failed': 0, 'conflicts': 1, ...}
Async Sync
import asyncio
from inventra import InventorySync
sync = InventorySync(channels=[shopify, amazon])
async def main():
report = await sync.async_sync()
print(report.summary())
asyncio.run(main())
Conflict Resolution Strategies
| Strategy | Behaviour |
|---|---|
LOWEST_STOCK |
Sync to the lowest quantity — prevents overselling (default) |
HIGHEST_STOCK |
Sync to the highest quantity |
LATEST_UPDATE |
Use the most recently updated channel |
CHANNEL_PRIORITY |
Respect a user-defined channel priority order |
MANUAL |
Raise ConflictError — let your code decide |
sync = InventorySync(conflict_resolution=ConflictResolution.CHANNEL_PRIORITY)
sync.set_channel_priority(["shopify", "amazon", "ebay"])
Building a Custom Channel Adapter
from inventra.channels.base import BaseChannelAdapter
from inventra.models import InventoryItem, SyncEvent, SyncStatus
class MyShopifyAdapter(BaseChannelAdapter):
channel_name = "shopify"
def connect(self):
# authenticate with Shopify API
self._connected = True
def disconnect(self):
self._connected = False
def get_inventory(self, skus=None):
# fetch from Shopify Inventory API
...
def update_inventory(self, item):
# push update to Shopify
return SyncEvent(sku=item.sku, channel=self.channel_name, status=SyncStatus.SUCCESS, new_quantity=item.quantity)
Advanced Features
Pipeline
from inventra import SyncPipeline
pipeline = (
SyncPipeline()
.filter(lambda item: item.quantity > 0, name="exclude_oos")
.map(lambda items: [i.model_copy(update={"reserved": 2}) for i in items], name="reserve_safety_stock")
.with_retry(count=2)
)
result = pipeline.run(items)
print(pipeline.audit_log())
Caching
from inventra import InventoryCache
cache = InventoryCache(max_size=500, ttl_seconds=120)
@cache.memoize
def expensive_fetch(channel_name):
...
print(cache.stats())
# {'hits': 10, 'misses': 2, 'hit_rate': 0.833, 'size': 1}
Diff & Change Detection
from inventra import diff_inventory
diff = diff_inventory(snapshot_before, snapshot_after)
print(diff.summary()) # {'added': 2, 'removed': 0, 'modified': 5}
print(diff.to_json())
Async Batch
from inventra import abatch_sync_items, CancellationToken
token = CancellationToken()
events = await abatch_sync_items(items, adapter.update_inventory, max_concurrency=8, token=token)
Rate Limiter
from inventra import RateLimiter
limiter = RateLimiter(rate=10, capacity=10) # 10 ops/sec
if limiter.acquire():
adapter.update_inventory(item)
Streaming
from inventra import inventory_to_ndjson
for line in inventory_to_ndjson(items):
socket.send(line)
Audit Log + PII Scrubbing
from inventra import AuditLog, PIIScrubber
log = AuditLog()
log.record("update", sku="SKU-001", channel="shopify", detail="qty 50→45")
metadata = {"note": "Customer email: user@example.com"}
clean = PIIScrubber.scrub_metadata(metadata)
# {'note': 'Customer email: [EMAIL]'}
Supported Channels
inventra ships with a MockChannelAdapter for testing. Community and official adapters (Shopify, Amazon, eBay, WooCommerce, Etsy) can be built by subclassing BaseChannelAdapter.
Changelog
v1.2.2 (2026-04-10)
- Added Contributing and Author sections to README
v1.2.1 (2026-04-10)
- Added Changelog section to README for release traceability
v1.2.0
- Added
ReorderPointCalculator— demand-driven reorder point and safety stock calculations - Added
InventoryABCAnalyzer— ABC/XYZ segmentation for inventory prioritisation - Expanded SEO keywords for PyPI discoverability
v1.0.1
- Advanced features: pipeline, caching, validation, diff/trend, streaming, audit log
v1.0.0
- Initial release: multi-channel inventory sync, conflict resolution, drift detection, audit log
License
MIT
Contributing
Contributions are welcome! Here's how to get started:
- Fork the repository on GitHub
- Create a feature branch:
git checkout -b feature/your-feature - Make your changes and add tests
- Run the test suite:
pytest tests/ -v - Submit a pull request
Please open an issue first for major changes to discuss the approach.
Author
Mahesh Makvana — GitHub · PyPI
MIT License
Metadata
Release files for inventra 1.2.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| inventra-1.2.2.tar.gz | 27.7 kB | Without provenance | Details for inventra-1.2.2.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| inventra-1.2.2-py3-none-any.whl | Python 3 | none | any | Details for inventra-1.2.2-py3-none-any.whl |
Total release size: 52.5 kB
Release files / inventra-1.2.2.tar.gz
| Download URL | inventra-1.2.2.tar.gz |
|---|---|
| Size | 27.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
a367245672b5dc359913a5c665a17b65e94866a373c1e0266981bbb0bb440df8
|
|
BLAKE2b-256 checksum How to use checksums |
85ea2df6b1af91c326d09da0c28b40b03f0e97a558c71f7d8a43fceca3ccd8ab
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.11.9
|
Release files / inventra-1.2.2-py3-none-any.whl
| Download URL | inventra-1.2.2-py3-none-any.whl |
|---|---|
| Size | 24.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
4d01220c0beaf79190c657ef301e701a311eacd659350f78b957f26ed4a2e575
|
|
BLAKE2b-256 checksum How to use checksums |
44e60861917bb009389b0591cd81cc47abdb1ef10fb6429005fe882f43005aba
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.11.9
|
Security for inventra 1.2.2
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.