acumatica-cli 0.12.0__tar.gz → 0.12.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {acumatica_cli-0.12.0 → acumatica_cli-0.12.1}/PKG-INFO +1 -1
- {acumatica_cli-0.12.0 → acumatica_cli-0.12.1}/pyproject.toml +1 -1
- {acumatica_cli-0.12.0 → acumatica_cli-0.12.1}/src/acumatica_cli/cli.py +89 -33
- {acumatica_cli-0.12.0 → acumatica_cli-0.12.1}/src/acumatica_cli/client.py +55 -9
- {acumatica_cli-0.12.0 → acumatica_cli-0.12.1}/README.md +0 -0
- {acumatica_cli-0.12.0 → acumatica_cli-0.12.1}/src/acumatica_cli/__init__.py +0 -0
- {acumatica_cli-0.12.0 → acumatica_cli-0.12.1}/src/acumatica_cli/bootstrap.py +0 -0
- {acumatica_cli-0.12.0 → acumatica_cli-0.12.1}/src/acumatica_cli/bootstrap_plugin.cs +0 -0
- {acumatica_cli-0.12.0 → acumatica_cli-0.12.1}/src/acumatica_cli/bootstrap_project.xml +0 -0
- {acumatica_cli-0.12.0 → acumatica_cli-0.12.1}/src/acumatica_cli/config.py +0 -0
- {acumatica_cli-0.12.0 → acumatica_cli-0.12.1}/src/acumatica_cli/extract.py +0 -0
- {acumatica_cli-0.12.0 → acumatica_cli-0.12.1}/src/acumatica_cli/extract_manifest.yaml +0 -0
- {acumatica_cli-0.12.0 → acumatica_cli-0.12.1}/src/acumatica_cli/firstlogin.py +0 -0
- {acumatica_cli-0.12.0 → acumatica_cli-0.12.1}/src/acumatica_cli/models.py +0 -0
- {acumatica_cli-0.12.0 → acumatica_cli-0.12.1}/src/acumatica_cli/output.py +0 -0
- {acumatica_cli-0.12.0 → acumatica_cli-0.12.1}/src/acumatica_cli/run.py +0 -0
- {acumatica_cli-0.12.0 → acumatica_cli-0.12.1}/src/acumatica_cli/seed.py +0 -0
- {acumatica_cli-0.12.0 → acumatica_cli-0.12.1}/src/acumatica_cli/target.py +0 -0
- {acumatica_cli-0.12.0 → acumatica_cli-0.12.1}/src/acumatica_cli/templates/distribution/README.md +0 -0
- {acumatica_cli-0.12.0 → acumatica_cli-0.12.1}/src/acumatica_cli/templates/distribution/baseline/20-accounts.yaml +0 -0
- {acumatica_cli-0.12.0 → acumatica_cli-0.12.1}/src/acumatica_cli/templates/distribution/baseline/60-ledger-company.yaml +0 -0
- {acumatica_cli-0.12.0 → acumatica_cli-0.12.1}/src/acumatica_cli/templates/distribution/baseline/90-uoms.yaml +0 -0
- {acumatica_cli-0.12.0 → acumatica_cli-0.12.1}/src/acumatica_cli/templates/distribution/bootstrap/company.yaml +0 -0
- {acumatica_cli-0.12.0 → acumatica_cli-0.12.1}/src/acumatica_cli/templates/distribution/bootstrap/features.yaml +0 -0
- {acumatica_cli-0.12.0 → acumatica_cli-0.12.1}/src/acumatica_cli/templates/distribution/master/10-reason-codes.yaml +0 -0
- {acumatica_cli-0.12.0 → acumatica_cli-0.12.1}/src/acumatica_cli/templates/distribution/master/20-in-preferences.yaml +0 -0
- {acumatica_cli-0.12.0 → acumatica_cli-0.12.1}/src/acumatica_cli/templates/distribution/master/30-availability-rules.yaml +0 -0
- {acumatica_cli-0.12.0 → acumatica_cli-0.12.1}/src/acumatica_cli/templates/distribution/master/40-posting-classes.yaml +0 -0
- {acumatica_cli-0.12.0 → acumatica_cli-0.12.1}/src/acumatica_cli/templates/distribution/master/50-warehouse.yaml +0 -0
- {acumatica_cli-0.12.0 → acumatica_cli-0.12.1}/src/acumatica_cli/templates/distribution/master/51-warehouse-locations.yaml +0 -0
- {acumatica_cli-0.12.0 → acumatica_cli-0.12.1}/src/acumatica_cli/templates/distribution/master/52-warehouse-defaults.yaml +0 -0
- {acumatica_cli-0.12.0 → acumatica_cli-0.12.1}/src/acumatica_cli/templates/distribution/master/53-tax-categories.yaml +0 -0
- {acumatica_cli-0.12.0 → acumatica_cli-0.12.1}/src/acumatica_cli/templates/distribution/master/54-item-classes.yaml +0 -0
- {acumatica_cli-0.12.0 → acumatica_cli-0.12.1}/src/acumatica_cli/templates/distribution/master/56-so-preferences.yaml +0 -0
- {acumatica_cli-0.12.0 → acumatica_cli-0.12.1}/src/acumatica_cli/templates/distribution/master/57-po-preferences.yaml +0 -0
- {acumatica_cli-0.12.0 → acumatica_cli-0.12.1}/src/acumatica_cli/templates/distribution/master/58-order-types.yaml +0 -0
- {acumatica_cli-0.12.0 → acumatica_cli-0.12.1}/src/acumatica_cli/templates/distribution/master/60-ar-preferences.yaml +0 -0
- {acumatica_cli-0.12.0 → acumatica_cli-0.12.1}/src/acumatica_cli/templates/distribution/master/61-ap-preferences.yaml +0 -0
- {acumatica_cli-0.12.0 → acumatica_cli-0.12.1}/src/acumatica_cli/templates/distribution/master/62-ca-preferences.yaml +0 -0
- {acumatica_cli-0.12.0 → acumatica_cli-0.12.1}/src/acumatica_cli/templates/distribution/master/63-cash-account.yaml +0 -0
- {acumatica_cli-0.12.0 → acumatica_cli-0.12.1}/src/acumatica_cli/templates/distribution/master/64-payment-methods.yaml +0 -0
- {acumatica_cli-0.12.0 → acumatica_cli-0.12.1}/src/acumatica_cli/templates/distribution/master/65-statement-cycles.yaml +0 -0
- {acumatica_cli-0.12.0 → acumatica_cli-0.12.1}/src/acumatica_cli/templates/distribution/master/70-vendor-classes.yaml +0 -0
- {acumatica_cli-0.12.0 → acumatica_cli-0.12.1}/src/acumatica_cli/templates/distribution/master/71-customer-classes.yaml +0 -0
- {acumatica_cli-0.12.0 → acumatica_cli-0.12.1}/src/acumatica_cli/templates/distribution/master/75-vendors.yaml +0 -0
- {acumatica_cli-0.12.0 → acumatica_cli-0.12.1}/src/acumatica_cli/templates/distribution/master/76-customers.yaml +0 -0
- {acumatica_cli-0.12.0 → acumatica_cli-0.12.1}/src/acumatica_cli/templates/distribution/master/80-stock-items-parts.yaml +0 -0
- {acumatica_cli-0.12.0 → acumatica_cli-0.12.1}/src/acumatica_cli/templates/distribution/master/82-stock-items-kits.yaml +0 -0
- {acumatica_cli-0.12.0 → acumatica_cli-0.12.1}/src/acumatica_cli/templates/distribution/master/85-kit-specifications.yaml +0 -0
- {acumatica_cli-0.12.0 → acumatica_cli-0.12.1}/src/acumatica_cli/templates/distribution/scenario/10-seed-capital.yaml +0 -0
- {acumatica_cli-0.12.0 → acumatica_cli-0.12.1}/src/acumatica_cli/templates/distribution/scenario/20-buy-gateways.yaml +0 -0
- {acumatica_cli-0.12.0 → acumatica_cli-0.12.1}/src/acumatica_cli/templates/distribution/scenario/30-build.yaml +0 -0
- {acumatica_cli-0.12.0 → acumatica_cli-0.12.1}/src/acumatica_cli/templates/distribution/scenario/40-sell.yaml +0 -0
- {acumatica_cli-0.12.0 → acumatica_cli-0.12.1}/src/acumatica_cli/templates/distribution/setup/30-open-periods.yaml +0 -0
- {acumatica_cli-0.12.0 → acumatica_cli-0.12.1}/src/acumatica_cli/templates/finance/baseline/10-subaccounts.yaml +0 -0
- {acumatica_cli-0.12.0 → acumatica_cli-0.12.1}/src/acumatica_cli/templates/finance/baseline/20-accounts.yaml +0 -0
- {acumatica_cli-0.12.0 → acumatica_cli-0.12.1}/src/acumatica_cli/templates/finance/baseline/40-ledger.yaml +0 -0
- {acumatica_cli-0.12.0 → acumatica_cli-0.12.1}/src/acumatica_cli/templates/finance/baseline/50-gl-preferences.yaml +0 -0
- {acumatica_cli-0.12.0 → acumatica_cli-0.12.1}/src/acumatica_cli/templates/finance/baseline/60-ledger-company.yaml +0 -0
- {acumatica_cli-0.12.0 → acumatica_cli-0.12.1}/src/acumatica_cli/templates/finance/baseline/90-uoms.yaml +0 -0
- {acumatica_cli-0.12.0 → acumatica_cli-0.12.1}/src/acumatica_cli/templates/finance/bootstrap/company.yaml +0 -0
- {acumatica_cli-0.12.0 → acumatica_cli-0.12.1}/src/acumatica_cli/templates/finance/bootstrap/credit-terms.yaml +0 -0
- {acumatica_cli-0.12.0 → acumatica_cli-0.12.1}/src/acumatica_cli/templates/finance/bootstrap/features.yaml +0 -0
- {acumatica_cli-0.12.0 → acumatica_cli-0.12.1}/src/acumatica_cli/templates/finance/env +0 -0
- {acumatica_cli-0.12.0 → acumatica_cli-0.12.1}/src/acumatica_cli/templates/finance/gitignore +0 -0
- {acumatica_cli-0.12.0 → acumatica_cli-0.12.1}/src/acumatica_cli/templates/finance/setup/10-financial-year.yaml +0 -0
- {acumatica_cli-0.12.0 → acumatica_cli-0.12.1}/src/acumatica_cli/templates/finance/setup/20-master-calendar.yaml +0 -0
- {acumatica_cli-0.12.0 → acumatica_cli-0.12.1}/src/acumatica_cli/templates/finance/setup/30-open-periods.yaml +0 -0
- {acumatica_cli-0.12.0 → acumatica_cli-0.12.1}/src/acumatica_cli/templates/finance/target +0 -0
- {acumatica_cli-0.12.0 → acumatica_cli-0.12.1}/src/acumatica_cli/tenant.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "acumatica-cli"
|
|
3
|
-
version = "0.12.
|
|
3
|
+
version = "0.12.1"
|
|
4
4
|
description = "Acumatica ERP Config-as-Code: tenant provisioning, baseline config, and reference data"
|
|
5
5
|
authors = [{ name = "Konstantin Borovik", email = "kb@lab5.ca" }]
|
|
6
6
|
license = "PolyForm-Noncommercial-1.0.0"
|
|
@@ -537,37 +537,73 @@ def config_check(ctx: click.Context, strict: bool) -> None:
|
|
|
537
537
|
# both live probes run through the exact objects live commands use, so
|
|
538
538
|
# a pass here proves the real code path, not a parallel one
|
|
539
539
|
inst = _resolve_instance(ctx)
|
|
540
|
-
failed = _probe_target(root, inst, strict=strict)
|
|
540
|
+
failed, claimed_erp = _probe_target(root, inst, strict=strict)
|
|
541
|
+
if not _probe_rest(inst, claimed_erp):
|
|
542
|
+
failed = True
|
|
543
|
+
if not _probe_ssh(inst):
|
|
544
|
+
failed = True
|
|
545
|
+
if failed:
|
|
546
|
+
raise SystemExit(1)
|
|
547
|
+
|
|
548
|
+
|
|
549
|
+
def _probe_rest(inst: Instance, claimed_erp: str | None) -> bool:
|
|
550
|
+
"""REST login + endpoints + optional ERP build; True = all passed.
|
|
551
|
+
|
|
552
|
+
Entering the client is the probe: login + landed-tenant verify (V5);
|
|
553
|
+
context manager guarantees logout (V6). One GET /entity feeds endpoints
|
|
554
|
+
(T74/T90) and optional ERP build (T92).
|
|
555
|
+
"""
|
|
541
556
|
try:
|
|
542
|
-
# entering the client is the whole probe: login + landed-tenant
|
|
543
|
-
# verify (V5), and the context manager guarantees logout (V6);
|
|
544
|
-
# endpoints probe (T74) reuses the same session when login succeeds
|
|
545
557
|
with AcumaticaClient(inst) as client:
|
|
546
558
|
output.data(f"ok rest ({inst.base_url}, tenant {inst.tenant})")
|
|
547
|
-
|
|
548
|
-
failed = True
|
|
559
|
+
return _probe_entity_root(client, inst, claimed_erp)
|
|
549
560
|
except (RuntimeError, httpx.HTTPError) as exc:
|
|
550
561
|
output.data(f"fail rest: {exc}")
|
|
551
|
-
|
|
562
|
+
return False
|
|
563
|
+
|
|
564
|
+
|
|
565
|
+
def _probe_entity_root(
|
|
566
|
+
client: AcumaticaClient, inst: Instance, claimed_erp: str | None
|
|
567
|
+
) -> bool:
|
|
568
|
+
"""Endpoints (+ ERP when claimed); True = all passed."""
|
|
569
|
+
ok = True
|
|
570
|
+
try:
|
|
571
|
+
endpoints, live_build = client.entity_root()
|
|
572
|
+
except (RuntimeError, httpx.HTTPError) as exc:
|
|
573
|
+
output.data(f"fail endpoints: {exc}")
|
|
574
|
+
endpoints, live_build = [], None
|
|
575
|
+
ok = False
|
|
576
|
+
else:
|
|
577
|
+
if not _probe_endpoints(endpoints, inst):
|
|
578
|
+
ok = False
|
|
579
|
+
if claimed_erp is not None and not _probe_erp(claimed_erp, live_build):
|
|
580
|
+
ok = False
|
|
581
|
+
return ok
|
|
582
|
+
|
|
583
|
+
|
|
584
|
+
def _probe_ssh(inst: Instance) -> bool:
|
|
585
|
+
"""SSH ping or skip when unset; True = pass/skip, False = fail."""
|
|
552
586
|
if not inst.ssh:
|
|
553
587
|
# V3/I.cmd: ACU_SSH optional — hosted data-plane path skips, never fails
|
|
554
588
|
output.data("skip ssh (ACU_SSH not set)")
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
raise SystemExit(1)
|
|
589
|
+
return True
|
|
590
|
+
try:
|
|
591
|
+
TenantManager(inst).ping()
|
|
592
|
+
output.data(f"ok ssh ({inst.ssh})")
|
|
593
|
+
return True
|
|
594
|
+
except RuntimeError as exc:
|
|
595
|
+
output.data(f"fail ssh: {exc}")
|
|
596
|
+
return False
|
|
564
597
|
|
|
565
598
|
|
|
566
|
-
def _probe_target(
|
|
567
|
-
|
|
599
|
+
def _probe_target(
|
|
600
|
+
root: Path | None, inst: Instance, *, strict: bool
|
|
601
|
+
) -> tuple[bool, str | None]:
|
|
602
|
+
"""Emit local target probe; return ``(failed, claimed_erp?)`` (V27).
|
|
568
603
|
|
|
569
604
|
Invalid target hard-exits (any loader). Missing under data root warns
|
|
570
|
-
unless --strict. No data root → skip. Match → ok
|
|
605
|
+
unless --strict. No data root → skip. Match → ok + claimed ``erp`` for
|
|
606
|
+
the live ERP probe after REST (T92).
|
|
571
607
|
"""
|
|
572
608
|
try:
|
|
573
609
|
target = load_target(root)
|
|
@@ -576,7 +612,7 @@ def _probe_target(root: Path | None, inst: Instance, *, strict: bool) -> bool:
|
|
|
576
612
|
raise SystemExit(1) from exc
|
|
577
613
|
if root is None:
|
|
578
614
|
output.data("skip target (no data root)")
|
|
579
|
-
return False
|
|
615
|
+
return False, None
|
|
580
616
|
if target is None:
|
|
581
617
|
msg = (
|
|
582
618
|
f"target: no target.yaml under {root} - dataset verified matrix "
|
|
@@ -584,35 +620,27 @@ def _probe_target(root: Path | None, inst: Instance, *, strict: bool) -> bool:
|
|
|
584
620
|
"to require it"
|
|
585
621
|
)
|
|
586
622
|
output.data(f"{'fail' if strict else 'warn'} {msg}")
|
|
587
|
-
return strict
|
|
623
|
+
return strict, None
|
|
588
624
|
if target.default_api != inst.api_version:
|
|
589
625
|
output.data(
|
|
590
626
|
f"fail target: dataset default_api={target.default_api} vs "
|
|
591
627
|
f"configured {inst.api_version}"
|
|
592
628
|
)
|
|
593
|
-
return True
|
|
629
|
+
return True, None
|
|
594
630
|
output.data(
|
|
595
631
|
f"ok target (default_api={target.default_api} matches configured; "
|
|
596
632
|
f"erp={target.erp} claimed)"
|
|
597
633
|
)
|
|
598
|
-
|
|
599
|
-
# skip rather than invent SSH/sqlcmd (control-plane exclusion, V1)
|
|
600
|
-
output.data(f"skip erp (live probe not available; claimed {target.erp})")
|
|
601
|
-
return False
|
|
634
|
+
return False, target.erp
|
|
602
635
|
|
|
603
636
|
|
|
604
|
-
def _probe_endpoints(
|
|
637
|
+
def _probe_endpoints(endpoints: list[tuple[str, str]], inst: Instance) -> bool:
|
|
605
638
|
"""Emit endpoints probe; return True on pass, False on fail (V12/V27).
|
|
606
639
|
|
|
607
640
|
Exact match: a Default entry whose version half equals
|
|
608
|
-
``Instance.api_version``.
|
|
641
|
+
``Instance.api_version``. Caller already fail-closed on unparseable GET.
|
|
609
642
|
"""
|
|
610
643
|
want = f"Default/{inst.api_version}"
|
|
611
|
-
try:
|
|
612
|
-
endpoints = client.list_endpoints()
|
|
613
|
-
except (RuntimeError, httpx.HTTPError) as exc:
|
|
614
|
-
output.data(f"fail endpoints: {exc}")
|
|
615
|
-
return False
|
|
616
644
|
defaults = [v for name, v in endpoints if name == "Default"]
|
|
617
645
|
if inst.api_version in defaults:
|
|
618
646
|
output.data(f"ok endpoints ({want} present)")
|
|
@@ -625,6 +653,34 @@ def _probe_endpoints(client: AcumaticaClient, inst: Instance) -> bool:
|
|
|
625
653
|
return False
|
|
626
654
|
|
|
627
655
|
|
|
656
|
+
def _major_minor(version: str) -> str:
|
|
657
|
+
"""First two dotted segments (T76/T92 major.minor match)."""
|
|
658
|
+
parts = version.split(".")
|
|
659
|
+
if len(parts) >= 2:
|
|
660
|
+
return f"{parts[0]}.{parts[1]}"
|
|
661
|
+
return version
|
|
662
|
+
|
|
663
|
+
|
|
664
|
+
def _probe_erp(claimed: str, live: str | None) -> bool:
|
|
665
|
+
"""Emit ERP probe; return True on pass/skip, False on fail (T92).
|
|
666
|
+
|
|
667
|
+
Live id comes from 26.x ``GET /entity`` wrapper
|
|
668
|
+
``version.acumaticaBuildVersion``. Bare array → skip (no HTTP surface).
|
|
669
|
+
Compare major.minor only — patch builds may drift within a claimed line.
|
|
670
|
+
"""
|
|
671
|
+
if live is None:
|
|
672
|
+
output.data(f"skip erp (live probe not available; claimed {claimed})")
|
|
673
|
+
return True
|
|
674
|
+
if _major_minor(live) == _major_minor(claimed):
|
|
675
|
+
output.data(f"ok erp ({live} matches claimed {claimed})")
|
|
676
|
+
return True
|
|
677
|
+
output.data(
|
|
678
|
+
f"fail erp: live {live} vs claimed {claimed} "
|
|
679
|
+
f"(major.minor {_major_minor(live)} vs {_major_minor(claimed)})"
|
|
680
|
+
)
|
|
681
|
+
return False
|
|
682
|
+
|
|
683
|
+
|
|
628
684
|
SEED_DIRS = ("bootstrap", "baseline", "setup", "master")
|
|
629
685
|
|
|
630
686
|
|
|
@@ -65,10 +65,22 @@ def unwrap(entity: dict[str, Any]) -> dict[str, Any]:
|
|
|
65
65
|
def parse_entity_list(response: httpx.Response) -> list[tuple[str, str]]:
|
|
66
66
|
"""Parse ``GET /entity`` into ``[(name, version), ...]`` (fail-closed).
|
|
67
67
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
shape change is re-verified
|
|
68
|
+
Dual shape (V31 / gh #20): top-level JSON array (legacy) or object with
|
|
69
|
+
an ``endpoints`` array (26.x wrapper). Each row needs string ``name``
|
|
70
|
+
and ``version``. Unparseable body → RuntimeError with status,
|
|
71
|
+
content-type, and a short raw hint so a shape change is re-verified
|
|
72
|
+
(V12) rather than silently skipped.
|
|
73
|
+
"""
|
|
74
|
+
return parse_entity_response(response)[0]
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def parse_entity_response(
|
|
78
|
+
response: httpx.Response,
|
|
79
|
+
) -> tuple[list[tuple[str, str]], str | None]:
|
|
80
|
+
"""Parse ``GET /entity`` → ``(endpoints, acumatica_build_version?)``.
|
|
81
|
+
|
|
82
|
+
Endpoints dual-shape per V31. Build id is ``version.acumaticaBuildVersion``
|
|
83
|
+
on the 26.x wrapper only; bare array → ``None`` (T92).
|
|
72
84
|
"""
|
|
73
85
|
try:
|
|
74
86
|
body = response.json()
|
|
@@ -80,15 +92,17 @@ def parse_entity_list(response: httpx.Response) -> list[tuple[str, str]]:
|
|
|
80
92
|
f"content-type {response.headers.get('content-type', '?')}; "
|
|
81
93
|
f"first 200 chars: {hint})"
|
|
82
94
|
) from exc
|
|
83
|
-
|
|
95
|
+
rows = _entity_list_rows(body)
|
|
96
|
+
if rows is None:
|
|
84
97
|
hint = str(body)[:200]
|
|
85
98
|
raise RuntimeError(
|
|
86
99
|
"GET /entity response not parseable as endpoint list "
|
|
87
|
-
f"(status {response.status_code}; expected non-empty JSON array
|
|
100
|
+
f"(status {response.status_code}; expected non-empty JSON array "
|
|
101
|
+
"or object with non-empty 'endpoints' array; "
|
|
88
102
|
f"first 200 chars: {hint})"
|
|
89
103
|
)
|
|
90
104
|
out: list[tuple[str, str]] = []
|
|
91
|
-
for item in
|
|
105
|
+
for item in rows:
|
|
92
106
|
if not isinstance(item, dict):
|
|
93
107
|
raise RuntimeError(
|
|
94
108
|
"GET /entity response not parseable as endpoint list "
|
|
@@ -102,7 +116,31 @@ def parse_entity_list(response: httpx.Response) -> list[tuple[str, str]]:
|
|
|
102
116
|
f"(row missing string name/version: {item!r})"
|
|
103
117
|
)
|
|
104
118
|
out.append((name, version))
|
|
105
|
-
return out
|
|
119
|
+
return out, _entity_build_version(body)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def _entity_list_rows(body: Any) -> list[Any] | None:
|
|
123
|
+
"""Return endpoint row list from array or 26.x wrapper; else None."""
|
|
124
|
+
if isinstance(body, list) and body:
|
|
125
|
+
return body
|
|
126
|
+
if isinstance(body, dict):
|
|
127
|
+
endpoints = body.get("endpoints")
|
|
128
|
+
if isinstance(endpoints, list) and endpoints:
|
|
129
|
+
return endpoints
|
|
130
|
+
return None
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def _entity_build_version(body: Any) -> str | None:
|
|
134
|
+
"""``version.acumaticaBuildVersion`` from 26.x wrapper; else None."""
|
|
135
|
+
if not isinstance(body, dict):
|
|
136
|
+
return None
|
|
137
|
+
version = body.get("version")
|
|
138
|
+
if not isinstance(version, dict):
|
|
139
|
+
return None
|
|
140
|
+
build = version.get("acumaticaBuildVersion")
|
|
141
|
+
if isinstance(build, str) and build:
|
|
142
|
+
return build
|
|
143
|
+
return None
|
|
106
144
|
|
|
107
145
|
|
|
108
146
|
# The list GET's optimized-export failure (B9): the contract API's list GET
|
|
@@ -245,8 +283,16 @@ class AcumaticaClient:
|
|
|
245
283
|
|
|
246
284
|
Fail-closed on unparseable body — see ``parse_entity_list``.
|
|
247
285
|
"""
|
|
286
|
+
return self.entity_root()[0]
|
|
287
|
+
|
|
288
|
+
def entity_root(self) -> tuple[list[tuple[str, str]], str | None]:
|
|
289
|
+
"""Authenticated ``GET /entity`` → endpoints + optional ERP build id.
|
|
290
|
+
|
|
291
|
+
Build id is present only on the 26.x wrapper
|
|
292
|
+
(``version.acumaticaBuildVersion``). Bare array → ``None`` (T92).
|
|
293
|
+
"""
|
|
248
294
|
r = self._checked(self._http.get("/entity"))
|
|
249
|
-
return
|
|
295
|
+
return parse_entity_response(r)
|
|
250
296
|
|
|
251
297
|
@staticmethod
|
|
252
298
|
def _checked(r: httpx.Response) -> httpx.Response:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{acumatica_cli-0.12.0 → acumatica_cli-0.12.1}/src/acumatica_cli/templates/distribution/README.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|