Multi-channel inventory sync for eCommerce — real-time conflict resolution, reorder point calculation, ABC/XYZ inventory analysis, demand forecasting, oversell prevention
Project description
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.
License
MIT
Project details
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 inventra-1.2.0.tar.gz.
File metadata
- Download URL: inventra-1.2.0.tar.gz
- Upload date:
- Size: 26.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
06a2876731856772d151bb065730cdbdd398b3727b2f2002f2d8471ae139693f
|
|
| MD5 |
023a724299f1c29c6a8ec7a65a3e7a2c
|
|
| BLAKE2b-256 |
c0db5f3c63e957de58ea2f25ec27179cd1a4d626d4ecba58835bb19ab0dcbb34
|
File details
Details for the file inventra-1.2.0-py3-none-any.whl.
File metadata
- Download URL: inventra-1.2.0-py3-none-any.whl
- Upload date:
- Size: 24.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
23e435fa6f5fb47113d312999083928962f4cac80c2543739b19baf9f40e3284
|
|
| MD5 |
e9e6988b8573b4a6b17e14360da2832f
|
|
| BLAKE2b-256 |
4853939cb644ef79d6c23e7ac5c0e02948364fa11c0891ab8ef7e4b3d61a392a
|