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.
@@ -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/
@@ -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,5 @@
1
+ Metadata-Version: 2.4
2
+ Name: ibm5250
3
+ Version: 0.1.0
4
+ Summary: IBM 5250 terminal automation library
5
+ Requires-Python: >=3.11
@@ -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"