Spencer McGuire spencerm
  • Joined on 2026-06-16

visualkit (0.1.0)

Published 2026-10-15 19:15:07 +00:00 by spencerm

Installation

pip install --index-url https://gt.pescoinc.biz/api/packages/spencerm/pypi/simple/ --extra-index-url https://pypi.org/simple visualkit

About 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_ro exists: leave VISUAL_USER unset. VisualKit uses your Windows login (integrated auth). Keep to DEV and small queries.
  • With visualkit_ro: set VISUAL_USER=visualkit_ro, then either
    • store the password once in Windows Credential Manager: .venv\Scripts\keyring set visualkit visualkit_ro, or
    • set VISUAL_PASSWORD, or VISUAL_PASSWORD_FILE pointing at a file that holds only the password.

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 at VISUAL_MAX_ROWS rows (10,000) unless max_rows= says otherwise. Use stream() for big reads.
  • Every query has a timeout (VISUAL_TIMEOUT, 60 s).
  • Anything that isn't a single SELECT is 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
  • --since is required. --until is optional; without it there is no upper bound, like the Power BI report.
  • --status picks the work order statuses for rows (default F,R,U,C). --materials-status picks the bases and legs for materials (default F,R).
  • --no-materials leaves materials out. --no-orders leaves out orders_by_base, orders_by_project and order. --full adds the raw work_orders, operations, requirements, parts and orders sections; orders lists 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

  1. Bump __version__ in src/visualkit/__init__.py and add a CHANGELOG.md entry.
  2. Run the tests, then build:
    .venv\Scripts\python -m pytest
    .venv\Scripts\python -m build
    
  3. Upload to the Gitea package registry. This needs a Gitea access token with write:package scope; 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\*
    
  4. 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. pyodbc has a separate wheel per platform and Python version, so this step matters.
    • Resolving for another platform needs uv, because pip applies sys_platform conditions using the machine it runs on.
    • Repeat the steps below for x86_64-pc-windows-msvc with --platform win_amd64, and for x86_64-manylinux_2_17 with --platform manylinux2014_x86_64.
    • Repeat for each Python version 3.11 to 3.14.
    uv 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\*.whl
    
    Here requirements.in holds the single line visualkit==<version>. Remove that line from resolved.txt before downloading, since the package itself is uploaded from dist. For new dependency releases, pick versions at least 14 days old.
  5. Tag the release: git tag v0.1.0 and git push --tags.

Source: https://gt.pescoinc.biz/spencerm/VisualKit. Proprietary, PESCO internal.

Requirements

Requires Python: >=3.11
Details
PyPI
2026-10-15 19:15:07 +00:00
3
Proprietary - PESCO internal use
192 KiB
Assets (2)
Versions (6) View all
0.4.2 2026-10-20
0.4.1 2026-10-20
0.4.0 2026-10-20
0.3.0 2026-10-15
0.2.0 2026-10-15