ibm5250 0.1.0__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.
- ibm5250-0.1.0/.github/workflows/README.md +48 -0
- ibm5250-0.1.0/.github/workflows/publish.yml +112 -0
- ibm5250-0.1.0/.gitignore +20 -0
- ibm5250-0.1.0/PKG-INFO +5 -0
- ibm5250-0.1.0/pyproject.toml +27 -0
- ibm5250-0.1.0/scripts/compute_version.py +116 -0
- ibm5250-0.1.0/src/ibm5250/__init__.py +96 -0
- ibm5250-0.1.0/src/ibm5250/connection.py +702 -0
- ibm5250-0.1.0/src/ibm5250/constants.py +549 -0
- ibm5250-0.1.0/src/ibm5250/data-stream-logic.md +220 -0
- ibm5250-0.1.0/src/ibm5250/datastream.py +820 -0
- ibm5250-0.1.0/src/ibm5250/exceptions.py +39 -0
- ibm5250-0.1.0/src/ibm5250/screen.py +795 -0
- ibm5250-0.1.0/src/ibm5250/terminal.py +636 -0
- ibm5250-0.1.0/uv.lock +79 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# CI/CD: Build & Publish
|
|
2
|
+
|
|
3
|
+
`publish.yml` builds the `ibm5250` package with [`uv`](https://docs.astral.sh/uv/)
|
|
4
|
+
on every push and publishes it to PyPI using
|
|
5
|
+
[Trusted Publishing](https://docs.pypi.org/trusted-publishers/) (OIDC) — no API
|
|
6
|
+
tokens or secrets are stored in this repository.
|
|
7
|
+
|
|
8
|
+
## Branch behavior
|
|
9
|
+
|
|
10
|
+
| Branch | Test | Build | Publish | Version format |
|
|
11
|
+
|------------------------|:----:|:-----:|:-------:|--------------------------|
|
|
12
|
+
| `main` | ✅ | ✅ | ✅ (real PyPI, production) | `X.Y.Z` (patch auto-incremented from the latest published release for that `X.Y`) |
|
|
13
|
+
| `develop` | ✅ | ✅ | ✅ (real PyPI, development) | `X.Y.Z.devN` (`N` auto-incremented per base version) |
|
|
14
|
+
| any other branch | ✅ | ✅ | ❌ | n/a (build artifact only, not published) |
|
|
15
|
+
|
|
16
|
+
The base version (`project.version` in `pyproject.toml`) is **never modified in
|
|
17
|
+
the repository**. `scripts/compute_version.py` queries PyPI's public JSON API
|
|
18
|
+
(`https://pypi.org/pypi/ibm5250/json`) at build time to determine the next
|
|
19
|
+
version number, and `uv version` applies it only to the ephemeral CI checkout
|
|
20
|
+
before `uv build` runs.
|
|
21
|
+
|
|
22
|
+
## One-time setup required on PyPI
|
|
23
|
+
|
|
24
|
+
Trusted Publishing must be configured on the `ibm5250` project at
|
|
25
|
+
https://pypi.org (Project settings → Publishing) for **each** of the two
|
|
26
|
+
publishing workflows below, since they use separate GitHub Environments:
|
|
27
|
+
|
|
28
|
+
1. **Production publisher**
|
|
29
|
+
- Owner: `bengieeee` (or the appropriate GitHub org/user)
|
|
30
|
+
- Repository: `python5250`
|
|
31
|
+
- Workflow name: `publish.yml`
|
|
32
|
+
- Environment name: `pypi`
|
|
33
|
+
|
|
34
|
+
2. **Development publisher**
|
|
35
|
+
- Owner / Repository / Workflow name: same as above
|
|
36
|
+
- Environment name: `pypi-dev`
|
|
37
|
+
|
|
38
|
+
If the `ibm5250` project doesn't exist on PyPI yet, use PyPI's
|
|
39
|
+
["pending publisher"](https://docs.pypi.org/trusted-publishers/creating-a-project-through-oidc/)
|
|
40
|
+
flow to register the trusted publisher before the first release, which will
|
|
41
|
+
create the project automatically on first successful publish.
|
|
42
|
+
|
|
43
|
+
## GitHub Environments (recommended)
|
|
44
|
+
|
|
45
|
+
Create the `pypi` and `pypi-dev` environments under repo Settings → Environments.
|
|
46
|
+
Environments are optional for Trusted Publishing to function, but are
|
|
47
|
+
recommended so you can add protection rules (e.g. required reviewers) around
|
|
48
|
+
production PyPI publishes independently of development ones.
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
name: Build and Publish
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
|
|
6
|
+
permissions:
|
|
7
|
+
contents: read
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
name: Test
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
steps:
|
|
14
|
+
- name: Checkout
|
|
15
|
+
uses: actions/checkout@v4
|
|
16
|
+
|
|
17
|
+
- name: Install uv
|
|
18
|
+
uses: astral-sh/setup-uv@v5
|
|
19
|
+
with:
|
|
20
|
+
enable-cache: true
|
|
21
|
+
|
|
22
|
+
- name: Install dependencies
|
|
23
|
+
run: uv sync --locked
|
|
24
|
+
|
|
25
|
+
- name: Run tests
|
|
26
|
+
run: uv run pytest
|
|
27
|
+
|
|
28
|
+
build:
|
|
29
|
+
name: Build
|
|
30
|
+
needs: test
|
|
31
|
+
runs-on: ubuntu-latest
|
|
32
|
+
outputs:
|
|
33
|
+
version: ${{ steps.version.outputs.version }}
|
|
34
|
+
steps:
|
|
35
|
+
- name: Checkout
|
|
36
|
+
uses: actions/checkout@v4
|
|
37
|
+
|
|
38
|
+
- name: Install uv
|
|
39
|
+
uses: astral-sh/setup-uv@v5
|
|
40
|
+
with:
|
|
41
|
+
enable-cache: true
|
|
42
|
+
|
|
43
|
+
- name: Determine release channel
|
|
44
|
+
id: channel
|
|
45
|
+
run: |
|
|
46
|
+
if [ "${{ github.ref }}" = "refs/heads/main" ]; then
|
|
47
|
+
echo "channel=main" >> "$GITHUB_OUTPUT"
|
|
48
|
+
elif [ "${{ github.ref }}" = "refs/heads/develop" ]; then
|
|
49
|
+
echo "channel=develop" >> "$GITHUB_OUTPUT"
|
|
50
|
+
else
|
|
51
|
+
echo "channel=other" >> "$GITHUB_OUTPUT"
|
|
52
|
+
fi
|
|
53
|
+
|
|
54
|
+
- name: Compute version
|
|
55
|
+
id: version
|
|
56
|
+
run: |
|
|
57
|
+
version=$(python3 scripts/compute_version.py "${{ steps.channel.outputs.channel }}")
|
|
58
|
+
echo "Computed version: $version"
|
|
59
|
+
echo "version=$version" >> "$GITHUB_OUTPUT"
|
|
60
|
+
|
|
61
|
+
- name: Apply computed version
|
|
62
|
+
run: uv version --no-sync "${{ steps.version.outputs.version }}"
|
|
63
|
+
|
|
64
|
+
- name: Build package
|
|
65
|
+
run: uv build
|
|
66
|
+
|
|
67
|
+
- name: Upload build artifacts
|
|
68
|
+
uses: actions/upload-artifact@v4
|
|
69
|
+
with:
|
|
70
|
+
name: dist
|
|
71
|
+
path: dist/
|
|
72
|
+
if-no-files-found: error
|
|
73
|
+
|
|
74
|
+
publish-main:
|
|
75
|
+
name: Publish (production / main)
|
|
76
|
+
needs: build
|
|
77
|
+
if: github.ref == 'refs/heads/main'
|
|
78
|
+
runs-on: ubuntu-latest
|
|
79
|
+
environment: pypi
|
|
80
|
+
permissions:
|
|
81
|
+
id-token: write
|
|
82
|
+
steps:
|
|
83
|
+
- name: Download build artifacts
|
|
84
|
+
uses: actions/download-artifact@v4
|
|
85
|
+
with:
|
|
86
|
+
name: dist
|
|
87
|
+
path: dist/
|
|
88
|
+
|
|
89
|
+
- name: Publish to PyPI
|
|
90
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
91
|
+
with:
|
|
92
|
+
packages-dir: dist/
|
|
93
|
+
|
|
94
|
+
publish-develop:
|
|
95
|
+
name: Publish (development / develop)
|
|
96
|
+
needs: build
|
|
97
|
+
if: github.ref == 'refs/heads/develop'
|
|
98
|
+
runs-on: ubuntu-latest
|
|
99
|
+
environment: pypi-dev
|
|
100
|
+
permissions:
|
|
101
|
+
id-token: write
|
|
102
|
+
steps:
|
|
103
|
+
- name: Download build artifacts
|
|
104
|
+
uses: actions/download-artifact@v4
|
|
105
|
+
with:
|
|
106
|
+
name: dist
|
|
107
|
+
path: dist/
|
|
108
|
+
|
|
109
|
+
- name: Publish to PyPI
|
|
110
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
111
|
+
with:
|
|
112
|
+
packages-dir: dist/
|
ibm5250-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
### macOS Finder metadata
|
|
2
|
+
.DS_Store
|
|
3
|
+
|
|
4
|
+
# Python Byte-compiled files
|
|
5
|
+
__pycache__/
|
|
6
|
+
|
|
7
|
+
# Python Distribution / packaging
|
|
8
|
+
dist/
|
|
9
|
+
*.egg-info/
|
|
10
|
+
|
|
11
|
+
# Python dotenv environment variable files
|
|
12
|
+
.env
|
|
13
|
+
|
|
14
|
+
# Python Virtual environments
|
|
15
|
+
.venv
|
|
16
|
+
env/
|
|
17
|
+
venv/
|
|
18
|
+
|
|
19
|
+
# Python Caches
|
|
20
|
+
.pytest_cache/
|
ibm5250-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "ibm5250"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "IBM 5250 terminal automation library"
|
|
5
|
+
requires-python = ">=3.11"
|
|
6
|
+
|
|
7
|
+
[dependency-groups]
|
|
8
|
+
dev = ["pytest>=8"]
|
|
9
|
+
|
|
10
|
+
[build-system]
|
|
11
|
+
requires = ["hatchling"]
|
|
12
|
+
build-backend = "hatchling.build"
|
|
13
|
+
|
|
14
|
+
[tool.hatch.build]
|
|
15
|
+
# The repo's .gitignore excludes "*.py" (a pre-existing, unrelated quirk of this
|
|
16
|
+
# repo) which would otherwise cause Hatchling to skip all Python source files
|
|
17
|
+
# when it defaults to respecting VCS ignore rules. Disable that so real source
|
|
18
|
+
# files are always included in built artifacts.
|
|
19
|
+
ignore-vcs = true
|
|
20
|
+
|
|
21
|
+
[tool.hatch.build.targets.wheel]
|
|
22
|
+
packages = ["src/ibm5250"]
|
|
23
|
+
|
|
24
|
+
[tool.hatch.build.targets.sdist]
|
|
25
|
+
exclude = [
|
|
26
|
+
"src/ibm5250/tests",
|
|
27
|
+
]
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
"""Compute the version to publish for the current CI run.
|
|
4
|
+
|
|
5
|
+
The version in pyproject.toml is never bumped in the repository itself.
|
|
6
|
+
Instead, this script inspects the versions already published on PyPI and
|
|
7
|
+
derives the *next* version to use for this build:
|
|
8
|
+
|
|
9
|
+
- ``main`` -> bump the patch component of the highest published final
|
|
10
|
+
release that shares the pyproject.toml major.minor version.
|
|
11
|
+
If none exists yet, publish the pyproject.toml version as-is.
|
|
12
|
+
- ``develop`` -> keep the pyproject.toml version as the base and append the
|
|
13
|
+
next available PEP 440 ``devN`` suffix (e.g. ``0.1.0.dev3``).
|
|
14
|
+
- anything else -> not published, so just echo the pyproject.toml version.
|
|
15
|
+
|
|
16
|
+
Usage:
|
|
17
|
+
python scripts/compute_version.py <main|develop|other>
|
|
18
|
+
"""
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import json
|
|
22
|
+
import re
|
|
23
|
+
import sys
|
|
24
|
+
import tomllib
|
|
25
|
+
import urllib.error
|
|
26
|
+
import urllib.request
|
|
27
|
+
from pathlib import Path
|
|
28
|
+
|
|
29
|
+
PACKAGE_NAME = "ibm5250"
|
|
30
|
+
PYPI_JSON_URL = f"https://pypi.org/pypi/{PACKAGE_NAME}/json"
|
|
31
|
+
PYPROJECT_PATH = Path(__file__).resolve().parent.parent / "pyproject.toml"
|
|
32
|
+
|
|
33
|
+
FINAL_VERSION_RE = re.compile(r"^(\d+)\.(\d+)\.(\d+)$")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def read_base_version() -> str:
|
|
37
|
+
with PYPROJECT_PATH.open("rb") as f:
|
|
38
|
+
data = tomllib.load(f)
|
|
39
|
+
return data["project"]["version"]
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def fetch_published_versions() -> list[str]:
|
|
43
|
+
"""Return the list of versions already published on PyPI (or [] if none)."""
|
|
44
|
+
try:
|
|
45
|
+
with urllib.request.urlopen(PYPI_JSON_URL, timeout=15) as resp:
|
|
46
|
+
payload = json.load(resp)
|
|
47
|
+
return list(payload.get("releases", {}).keys())
|
|
48
|
+
except urllib.error.HTTPError as exc:
|
|
49
|
+
if exc.code == 404:
|
|
50
|
+
return []
|
|
51
|
+
raise
|
|
52
|
+
except urllib.error.URLError:
|
|
53
|
+
# Network hiccups shouldn't hard-fail version computation; treat as
|
|
54
|
+
# "nothing published yet" so the build can still proceed.
|
|
55
|
+
return []
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def next_main_version(base_version: str, published: list[str]) -> str:
|
|
59
|
+
base_match = FINAL_VERSION_RE.match(base_version)
|
|
60
|
+
if not base_match:
|
|
61
|
+
raise ValueError(f"Unsupported base version format: {base_version!r}")
|
|
62
|
+
base_major, base_minor, base_patch = (int(x) for x in base_match.groups())
|
|
63
|
+
|
|
64
|
+
best_patch = None
|
|
65
|
+
for version in published:
|
|
66
|
+
m = FINAL_VERSION_RE.match(version)
|
|
67
|
+
if not m:
|
|
68
|
+
continue
|
|
69
|
+
major, minor, patch = (int(x) for x in m.groups())
|
|
70
|
+
if (major, minor) != (base_major, base_minor):
|
|
71
|
+
continue
|
|
72
|
+
if best_patch is None or patch > best_patch:
|
|
73
|
+
best_patch = patch
|
|
74
|
+
|
|
75
|
+
if best_patch is None:
|
|
76
|
+
# Nothing published yet for this major.minor: use the base version as-is.
|
|
77
|
+
return f"{base_major}.{base_minor}.{base_patch}"
|
|
78
|
+
return f"{base_major}.{base_minor}.{best_patch + 1}"
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def next_develop_version(base_version: str, published: list[str]) -> str:
|
|
82
|
+
dev_re = re.compile(rf"^{re.escape(base_version)}\.dev(\d+)$")
|
|
83
|
+
best_dev = None
|
|
84
|
+
for version in published:
|
|
85
|
+
m = dev_re.match(version)
|
|
86
|
+
if not m:
|
|
87
|
+
continue
|
|
88
|
+
n = int(m.group(1))
|
|
89
|
+
if best_dev is None or n > best_dev:
|
|
90
|
+
best_dev = n
|
|
91
|
+
next_n = 0 if best_dev is None else best_dev + 1
|
|
92
|
+
return f"{base_version}.dev{next_n}"
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def main() -> None:
|
|
96
|
+
if len(sys.argv) != 2:
|
|
97
|
+
print(f"Usage: {sys.argv[0]} <main|develop|other>", file=sys.stderr)
|
|
98
|
+
sys.exit(1)
|
|
99
|
+
|
|
100
|
+
channel = sys.argv[1]
|
|
101
|
+
base_version = read_base_version()
|
|
102
|
+
|
|
103
|
+
if channel == "main":
|
|
104
|
+
published = fetch_published_versions()
|
|
105
|
+
version = next_main_version(base_version, published)
|
|
106
|
+
elif channel == "develop":
|
|
107
|
+
published = fetch_published_versions()
|
|
108
|
+
version = next_develop_version(base_version, published)
|
|
109
|
+
else:
|
|
110
|
+
version = base_version
|
|
111
|
+
|
|
112
|
+
print(version)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
if __name__ == "__main__":
|
|
116
|
+
main()
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"""ibm5250 — IBM 5250 terminal automation library.
|
|
2
|
+
|
|
3
|
+
Typical usage::
|
|
4
|
+
|
|
5
|
+
from ibm5250 import Terminal, AID
|
|
6
|
+
|
|
7
|
+
with Terminal("as400.example.com") as t:
|
|
8
|
+
t.wait_for_text("Sign On")
|
|
9
|
+
t.fill_field("User", "MYUSER")
|
|
10
|
+
t.fill_field("Password", "SECRET")
|
|
11
|
+
t.send_key(AID.ENTER)
|
|
12
|
+
t.wait_for_text("Main Menu")
|
|
13
|
+
print(t.screen.get_screen_text())
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from .constants import (
|
|
17
|
+
AID,
|
|
18
|
+
Command,
|
|
19
|
+
Order,
|
|
20
|
+
TelnetCmd,
|
|
21
|
+
TelnetOpt,
|
|
22
|
+
FFW,
|
|
23
|
+
SCREEN_ROWS,
|
|
24
|
+
SCREEN_COLS,
|
|
25
|
+
encode_addr,
|
|
26
|
+
decode_addr,
|
|
27
|
+
pos_to_rowcol,
|
|
28
|
+
rowcol_to_pos,
|
|
29
|
+
)
|
|
30
|
+
from .connection import TN5250Connection
|
|
31
|
+
from .datastream import (
|
|
32
|
+
DataStreamParser,
|
|
33
|
+
DSEvent,
|
|
34
|
+
DSEventType,
|
|
35
|
+
FieldResponse,
|
|
36
|
+
build_aid_response,
|
|
37
|
+
ebcdic_encode,
|
|
38
|
+
ebcdic_decode,
|
|
39
|
+
ebcdic_decode_stripped,
|
|
40
|
+
)
|
|
41
|
+
from .exceptions import (
|
|
42
|
+
IBM5250Error,
|
|
43
|
+
ConnectionError,
|
|
44
|
+
NegotiationError,
|
|
45
|
+
ProtocolError,
|
|
46
|
+
RecvTimeout,
|
|
47
|
+
ScreenWaitTimeout,
|
|
48
|
+
FieldNotFound,
|
|
49
|
+
KeyboardLocked,
|
|
50
|
+
)
|
|
51
|
+
from .screen import Field, Screen
|
|
52
|
+
from .terminal import FieldProxy, Terminal
|
|
53
|
+
|
|
54
|
+
__all__ = [
|
|
55
|
+
# High-level
|
|
56
|
+
"Terminal",
|
|
57
|
+
"FieldProxy",
|
|
58
|
+
# Screen / fields
|
|
59
|
+
"Screen",
|
|
60
|
+
"Field",
|
|
61
|
+
# Constants
|
|
62
|
+
"AID",
|
|
63
|
+
"Command",
|
|
64
|
+
"Order",
|
|
65
|
+
"TelnetCmd",
|
|
66
|
+
"TelnetOpt",
|
|
67
|
+
"FFW",
|
|
68
|
+
"SCREEN_ROWS",
|
|
69
|
+
"SCREEN_COLS",
|
|
70
|
+
# Address helpers
|
|
71
|
+
"encode_addr",
|
|
72
|
+
"decode_addr",
|
|
73
|
+
"pos_to_rowcol",
|
|
74
|
+
"rowcol_to_pos",
|
|
75
|
+
# Low-level
|
|
76
|
+
"TN5250Connection",
|
|
77
|
+
"DataStreamParser",
|
|
78
|
+
"DSEvent",
|
|
79
|
+
"DSEventType",
|
|
80
|
+
"FieldResponse",
|
|
81
|
+
"build_aid_response",
|
|
82
|
+
# EBCDIC helpers
|
|
83
|
+
"ebcdic_encode",
|
|
84
|
+
"ebcdic_decode",
|
|
85
|
+
"ebcdic_decode_stripped",
|
|
86
|
+
# Exceptions
|
|
87
|
+
"IBM5250Error",
|
|
88
|
+
"ConnectionError",
|
|
89
|
+
"NegotiationError",
|
|
90
|
+
"ProtocolError",
|
|
91
|
+
"ScreenWaitTimeout",
|
|
92
|
+
"FieldNotFound",
|
|
93
|
+
"KeyboardLocked",
|
|
94
|
+
]
|
|
95
|
+
|
|
96
|
+
__version__ = "0.1.0"
|