visualkit (0.1.0)
Installation
pip install --index-url https://gt.pescoinc.biz/api/packages/spencerm/pypi/simple/ --extra-index-url https://pypi.org/simple visualkitAbout this package
Read-only access to the Infor VISUAL Manufacturing 10 ERP database (SQL Server)
VisualKit
Read-only Python access to PESCO's Infor VISUAL Manufacturing 10 ERP database (SQL Server). See PLAN.md for the design and roadmap, and CHANGELOG.md for releases.
Using VisualKit in your own app or scheduled job? Start with docs/INSTALL.md. This README is for working on VisualKit itself.
VisualKit never changes anything in VISUAL. Only SELECT statements get through, and it connects to DEV unless production is chosen explicitly.
Install (development)
You need Python 3.11+ and Microsoft ODBC Driver 18 for SQL Server (64-bit). On Linux, see docs/INSTALL.md for the driver.
python -m venv .venv
.venv\Scripts\python -m pip install -e ".[dev,keyring]"
.venv\Scripts\visualkit doctor # checks settings, driver, encryption, auth, connectivity and that it can't write
On Linux, use .venv/bin/ instead of .venv\Scripts\, and leave out keyring.
Configure
Copy .env.example to .env. The defaults point at DEV (VSQL\DEV / PESCOINCDEV). Don't put the password in .env.
- Windows, before
visualkit_roexists: leaveVISUAL_USERunset. VisualKit uses your Windows login (integrated auth). Keep to DEV and small queries. - With
visualkit_ro: setVISUAL_USER=visualkit_ro, then either- store the password once in Windows Credential Manager:
.venv\Scripts\keyring set visualkit visualkit_ro, or - set
VISUAL_PASSWORD, orVISUAL_PASSWORD_FILEpointing at a file that holds only the password.
- store the password once in Windows Credential Manager:
Production (vsql / PESCOINC) is only used with VISUAL_PROFILE=prod, or with VISUAL_SERVER=vsql and VISUAL_DATABASE=PESCOINC both set. All settings are listed in docs/INSTALL.md.
Use
from visualkit import Visual
with Visual.from_env() as v:
print(v.server_info()) # SQL Server version, database, login, app name, VISUAL DBVERSION
for site in v.sites():
print(site)
rows = v.query("SELECT ID, DESCRIPTION FROM PART WHERE ID LIKE ?", ["L073%"], max_rows=100)
count = v.scalar("SELECT COUNT(*) FROM PART")
for row in v.stream("SELECT ID FROM PART"): # fetched in batches of VISUAL_BATCH_SIZE
...
query()stops atVISUAL_MAX_ROWSrows (10,000) unlessmax_rows=says otherwise. Usestream()for big reads.- Every query has a timeout (
VISUAL_TIMEOUT, 60 s). - Anything that isn't a single
SELECTis refused before it reaches the server. - ID columns come back as text. Keep them that way.
Domains
Five typed, read-only repositories hang off Visual. They return frozen pydantic models. The full reference is in docs/INSTALL.md.
from datetime import date
from visualkit import Visual, WorkOrderKey
with Visual.from_env() as v:
# Parts
part = v.parts.get("L073-76-2")
parts = v.parts.get_many(["L073-76-2", "L073-76-2GA"]) # {id: Part}
planning = v.parts.site("L073-76-2GA", "PESCO") # PART_SITE: price, primary location
# Customer orders
order, customer = v.orders.with_customer("51049")
lines = v.orders.lines("51049")
open_orders = v.orders.list(open_only=True, limit=50) # a Page
# Work orders
wo = v.work_orders.get(WorkOrderKey.parse("L073-75-1/53/0/0"))
ops = v.work_orders.operations(wo.key) # op.barcode() for Packet Station
feed = v.work_orders.released("PESCO", date(2026, 10, 1), date(2026, 10, 31))
# Inventory
stock = v.inventory.on_hand("L073-76-2GA", site_id="PESCO") # nonzero locations
# Shipments
packlists = v.shipments.for_order("51049")
shipped = v.shipments.for_order_line("51049", 1) # [] means not shipped yet
List calls return a Page: page.items, page.has_more, page.next_offset.
CLI
visualkit doctor | info | sites
visualkit parts get L073-76-2 L073-76-2GA # also: list, search, site
visualkit orders get 51049 # also: list, lines
visualkit wo get L073-75-1/53/0/0 # or a bare base ID: lot 1, split 0, sub 0
visualkit wo released --since 2026-10-01 --until 2026-11-14 --limit 5
visualkit wo barcode "*%2L073-75-1$53$0$0$10%*" --fetch
visualkit inventory onhand L073-76-2GA # also: list, warehouses, locations
visualkit ship get 77881 # also: list, for-order, for-line
Every data subcommand prints a table, or JSON with --json. Most take --site. List subcommands take --limit and --offset; when there are more rows, the next --offset goes to stderr. --width sets where long text is cut (40 by default). Dates are YYYY-MM-DD or ISO. Run visualkit <domain> <action> --help for the filters.
Exit codes: 0 fine, 1 not found or a VISUAL error, 2 bad settings or input. Production needs --allow-production.
Packet Station example
examples/packet_station_feed.py replaces the two Power BI feeds Packet Station reads today. It writes one JSON object with meta, rows (Job Release Information: one row per operation, with wo, lot, split, sub, seq, res, dwg, rev, ps, desc, qty, tgt, rel, want, st, plus barcode, part, site and order), materials (Job Pull: one row per material line, with wo, lot, split, sub, seq, piece, part, qty, issued, whs, loc, st, istat, dwg, desc, comm, cons), orders_by_base and orders_by_project.
Customer orders come from DEMAND_SUPPLY_LINK (v.links), not from part-number prefixes. orders_by_base holds the orders a base is pegged to directly; only whole-number top jobs (L489001) and field-service jobs are. orders_by_project holds the orders of each project's top jobs, found by PESCO's "project plus three digits" naming (L489 -> L489001); that is a naming convention, not a VISUAL relationship. Each row's order is the base's own order, else the project's first open order, else its newest order, else null, with via saying which. Order entries carry the order ID, status, customer ID and name, customer PO and dates; no amounts.
.venv\Scripts\python examples\packet_station_feed.py --since 2026-09-30 --out feed.json
--sinceis required.--untilis optional; without it there is no upper bound, like the Power BI report.--statuspicks the work order statuses for rows (defaultF,R,U,C).--materials-statuspicks the bases and legs for materials (defaultF,R).--no-materialsleaves materials out.--no-ordersleaves outorders_by_base,orders_by_projectandorder.--fulladds the rawwork_orders,operations,requirements,partsandorderssections;orderslists the directly linked orders with their bases and lines.- PARTSPECS (
ps) comes from OPERATION_BINARY; the text of material lines with no part comes from REQUIREMENT_BINARY. - Numbers are JSON numbers, dates
YYYY-MM-DD, IDs strings. No cost, price or pay field is written.
On DEV, since 2026-09-30 with no upper bound: 1,533 work orders, 5,962 rows and 16,956 material lines in about 7.5 seconds. Compared with the Power BI export of 2026-10-14, every matched field agrees except where DEV changed after the export (see PLAN.md section 8b).
Test
.venv\Scripts\python -m pytest
Tests use a fake cursor and need no database. Integration tests run against DEV only, when asked:
$env:VISUALKIT_INTEGRATION = "1"; .venv\Scripts\python -m pytest
Release
- Bump
__version__insrc/visualkit/__init__.pyand add aCHANGELOG.mdentry. - Run the tests, then build:
.venv\Scripts\python -m pytest .venv\Scripts\python -m build - Upload to the Gitea package registry. This needs a Gitea access token with
write:packagescope; paste it when twine asks for the password:.venv\Scripts\python -m twine upload --repository-url https://gt.pescoinc.biz/api/packages/spencerm/pypi -u spencerm dist\* - If any runtime dependency version changed, update
requirements/runtime-lock.txt, then mirror the dependencies to the registry for every platform and Python version, so installs keep working from PESCO's registry alone.pyodbchas a separate wheel per platform and Python version, so this step matters.- Resolving for another platform needs uv, because pip applies
sys_platformconditions using the machine it runs on. - Repeat the steps below for
x86_64-pc-windows-msvcwith--platform win_amd64, and forx86_64-manylinux_2_17with--platform manylinux2014_x86_64. - Repeat for each Python version 3.11 to 3.14.
Hereuv pip compile requirements.in -c requirements\runtime-lock.txt --find-links dist --python-platform x86_64-manylinux_2_17 --python-version 3.11 -o resolved.txt .venv\Scripts\python -m pip download -r resolved.txt --no-deps --only-binary=:all: --platform manylinux2014_x86_64 --implementation cp --python-version 3.11 -d wheelhouse .venv\Scripts\python -m twine upload --skip-existing --repository-url https://gt.pescoinc.biz/api/packages/spencerm/pypi -u spencerm wheelhouse\*.whlrequirements.inholds the single linevisualkit==<version>. Remove that line fromresolved.txtbefore downloading, since the package itself is uploaded fromdist. For new dependency releases, pick versions at least 14 days old. - Resolving for another platform needs uv, because pip applies
- Tag the release:
git tag v0.1.0andgit push --tags.
Source: https://gt.pescoinc.biz/spencerm/VisualKit. Proprietary, PESCO internal.