Pure Python 3270 emulator
Project description
Pure3270: Pure Python 3270 Terminal Emulation Library
Pure3270 is a self-contained, pure Python 3.8+ implementation of a 3270 terminal emulator, designed to emulate the functionality of the s3270 terminal emulator. It integrates seamlessly with the p3270 library through runtime monkey-patching, allowing you to replace p3270's dependency on the external s3270 binary without complex setup. The library uses standard asyncio for networking with no external telnet dependencies and supports TN3270 and TN3270E protocols, full 3270 emulation (screen buffer, fields, keyboard simulation), and optional SSL/TLS.
Recent updates include an async refactor in pure3270/session.py with AsyncSession supporting connect, macro execution, and managed context; exports in pure3270/__init__.py for Session, AsyncSession, and enable_replacement; enhanced tests with edge cases; and planned CI setup.
Key features:
- Zero-configuration opt-in: Call
enable_replacement()to patchp3270automatically. - Standalone usage: Use
SessionorAsyncSessiondirectly withoutp3270. - Pythonic API: Context managers, async support, and structured error handling.
- Compatibility: Mirrors
s3270andp3270interfaces with enhancements.
For architecture details, see architecture.md.
Installation
Pure3270 requires Python 3.8 or later. It is recommended to use a virtual environment for isolation.
1. Create and Activate Virtual Environment
Create a virtual environment in your project directory:
python -m venv .venv
Activate it:
- On Unix/macOS:
source .venv/bin/activate - On Windows:
.venv\Scripts\activate
2. Install Pure3270
No external dependencies are required beyond the Python standard library for core usage.
For development (editable install):
pip install -e .
For distribution (from source):
pip install .
This uses the existing setup.py, which specifies no external dependencies. Deactivate the venv with deactivate when done.
Development Dependencies
For testing and linting, install additional tools:
pip install pytest-cov black flake8
pytest-cov: For coverage reporting (e.g.,pytest --cov=pure3270).black: For code formatting (e.g.,black .).flake8: For linting (e.g.,flake8 .).
Exports
The main classes and functions are exported from the top-level module for easy import. From pure3270/__init__.py:
from pure3270 import Session, AsyncSession, enable_replacement
Quick Start Snippets
Enable Patching:
import pure3270
pure3270.enable_replacement() # Patches p3270 for seamless integration
Synchronous Session:
from pure3270 import Session
with Session() as session:
session.connect('your-host.example.com', port=23, ssl=False)
session.send('key Enter')
print(session.read())
Asynchronous Session:
import asyncio
from pure3270 import AsyncSession
async def main():
async with AsyncSession() as session:
await session.connect('your-host.example.com', port=23, ssl=False)
await session.send('key Enter')
print(await session.read())
asyncio.run(main())
Usage
Patching p3270 for Seamless Integration
To replace p3270's s3270 dependency with pure3270:
- Install
p3270in your venv:pip install p3270. - Enable patching before importing
p3270.
Example:
import pure3270
pure3270.enable_replacement() # Applies global patches to p3270
import p3270
session = p3270.P3270Client() # Now uses pure3270 under the hood
session.connect('your-host.example.com', port=23, ssl=False)
session.send('key Enter')
screen_text = session.read()
print(screen_text)
session.close()
This redirects p3270.P3270Client methods (__init__, connect, send, read) to pure3270 equivalents. Logs will indicate patching success.
Standalone Usage
Use pure3270 directly without p3270.
Synchronous Usage
From pure3270/session.py:
from pure3270 import Session
session = Session()
try:
session.connect('your-host.example.com', port=23, ssl=False)
session.send('key Enter')
print(session.read())
finally:
session.close()
Supports macros:
session.macro(['String(hello)', 'key Enter'])
Asynchronous Usage
From pure3270/session.py, AsyncSession provides async support for non-blocking operations.
Basic Connection and Send:
import asyncio
from pure3270 import AsyncSession
async def main():
async with AsyncSession() as session:
await session.connect('your-host.example.com', port=23, ssl=False)
await session.send('key Enter')
print(await session.read())
asyncio.run(main())
Executing Macros:
import asyncio
from pure3270 import AsyncSession
async def main():
async with AsyncSession() as session:
await session.connect('your-host.example.com', port=23, ssl=False)
await session.macro(['String(hello)', 'key Enter'])
print(await session.read())
asyncio.run(main())
Using Managed Context:
The managed context manager ensures proper session lifecycle:
import asyncio
from pure3270 import AsyncSession
async def main():
session = AsyncSession()
async with session.managed():
await session.connect('your-host.example.com', port=23, ssl=False)
await session.send('key Enter')
print(await session.read())
# Session is automatically closed here
asyncio.run(main())
Handling Errors: Use try-except for robust error handling:
import asyncio
from pure3270 import AsyncSession, SessionError
async def main():
try:
async with AsyncSession() as session:
await session.connect('your-host.example.com', port=23, ssl=False)
await session.send('key Enter')
print(await session.read())
except SessionError as e:
print(f"Session error: {e}")
except Exception as e:
print(f"Unexpected error: {e}")
asyncio.run(main())
See the examples/ directory for runnable scripts demonstrating these patterns.
API Reference
enable_replacement()
Top-level function to apply monkey patches to p3270 for transparent integration.
From pure3270/patching/patching.py:
def enable_replacement(
patch_sessions: bool = True,
patch_commands: bool = True,
strict_version: bool = False
) -> MonkeyPatchManager:
"""
Top-level API for zero-configuration opt-in patching.
Applies global patches to p3270 for seamless pure3270 integration.
Supports selective patching and fallback detection.
:param patch_sessions: Patch session initialization and methods (default True).
:param patch_commands: Patch command execution (default True).
:param strict_version: Raise error on version mismatch (default False).
:return: The MonkeyPatchManager instance for manual control.
:raises Pure3270PatchError: If strict and patching fails.
"""
Returns a MonkeyPatchManager for advanced control (e.g., manager.unpatch()).
Session
Synchronous session handler for 3270 connections.
From pure3270/session.py:
class Session:
"""
Synchronous 3270 session handler (wraps AsyncSession).
"""
def __init__(self, rows: int = 24, cols: int = 80, force_3270: bool = False):
"""
Initialize the Session.
:param rows: Screen rows (default 24).
:param cols: Screen columns (default 80).
:param force_3270: Force TN3270 mode without negotiation (for testing).
"""
def connect(self, host: str, port: int = 23, ssl: bool = False) -> None:
"""
Connect to the TN3270 host (sync).
:param host: Hostname or IP.
:param port: Port (default 23).
:param ssl: Use SSL/TLS if True.
:raises SessionError: If connection fails.
"""
def send(self, command: str) -> None:
"""
Send a command or key (sync).
:param command: Command or key (e.g., "key Enter", "String(hello)").
:raises SessionError: If send fails.
"""
def read(self) -> str:
"""
Read the current screen content (sync).
:return: Screen text as string.
:raises SessionError: If read fails.
"""
def macro(self, sequence: Sequence[str]) -> None:
"""
Execute a macro sequence (sync).
:param sequence: List of commands.
"""
def close(self) -> None:
"""
Close the session (sync).
"""
@property
def connected(self) -> bool:
"""
Check if connected.
"""
Supports context manager: with Session() as session: ... (auto-closes on exit).
Additional properties:
tn3270_mode: bool- Check if TN3270 mode is active.tn3270e_mode: bool- Check if TN3270E mode is active.lu_name: Optional[str]- Get the bound LU name.
AsyncSession
Asynchronous 3270 session handler.
From pure3270/session.py:
class AsyncSession:
"""Asynchronous 3270 session handler."""
def __init__(
self, rows: int = 24, cols: int = 80, force_3270: bool = False
):
"""
Initialize the AsyncSession.
:param rows: Screen rows (default 24).
:param cols: Screen columns (default 80).
:param force_3270: Force TN3270 mode without negotiation (for testing).
"""
async def connect(
self, host: str, port: int = 23, ssl: bool = False
) -> None:
"""
Connect to the TN3270 host.
:param host: Hostname or IP.
:param port: Port (default 23).
:param ssl: Use SSL/TLS if True.
:raises SessionError: If connection fails.
"""
async def send(self, command: str) -> None:
"""
Send a command or key to the host.
:param command: Command or key (e.g., "key Enter", "String(hello)").
:raises SessionError: If send fails.
"""
async def read(self) -> str:
"""
Read the current screen content.
:return: Screen text as string.
:raises SessionError: If read fails.
"""
async def macro(self, sequence: Sequence[str]) -> None:
"""
Execute a macro sequence of commands.
:param sequence: List of commands.
"""
async def close(self) -> None:
"""Close the session."""
@property
def connected(self) -> bool:
"""Check if connected."""
@asynccontextmanager
async def managed(self):
"""Context manager for the session."""
Supports async context manager: async with session.managed(): ... (auto-closes on exit).
Additional properties:
tn3270_mode: bool- Check if TN3270 mode is active.tn3270e_mode: bool- Check if TN3270E mode is active.lu_name: Optional[str]- Get the bound LU name.
Other Exports
setup_logging(level: str = "INFO"): Configure logging for the library.- Exceptions:
Pure3270Error,SessionError,ProtocolError,NegotiationError,ParseError,Pure3270PatchError.
For full details, refer to the source code or inline docstrings.
Testing
Pure3270 includes comprehensive tests in the tests/ directory, enhanced with edge cases for async operations, protocol handling, and patching.
Running Tests
Install dev dependencies (see Installation). Then:
pytest tests/ --cov=pure3270 --cov-report=html
This generates coverage reports and HTML output in htmlcov/.
For linting:
black . --check
flake8 .
CI Setup
To automate testing and linting, set up GitHub Actions. Create .github/workflows/ci.yml:
name: CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: 3.8
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -e .[dev]
- name: Run tests
run: pytest tests/ --cov=pure3270 --cov-report=xml
- name: Lint
run: |
black . --check
flake8 .
- name: Upload coverage
uses: codecov/codecov-action@v1
This runs tests, coverage, and linting on push/PR. Badges can be generated via services like Shields.io or Codecov for integration into the README.
Contribution Guidelines
Contributions are welcome! Please follow these steps:
- Fork the repository and create a feature branch.
- Install dev dependencies and run tests/linting locally.
- Make changes and add tests for new features.
- Ensure code passes
blackformatting andflake8linting. - Submit a pull request with a clear description of changes.
See the tests for examples. For major changes, open an issue first.
Migration Guide from s3270 / p3270
Pure3270 replaces the binary s3270 dependency in p3270 setups, eliminating the need for external installations (e.g., no compiling or downloading s3270 binaries).
Key Changes
- Binary Replacement via Patching: Call
pure3270.enable_replacement()before importingp3270. This monkey-patchesp3270.P3270Clientto delegate to pure3270'sSession, handling connections, sends, and reads internally using standard asyncio instead of spawnings3270processes. - Zero-Config Opt-In: No changes to your
p3270code required. The patching is global by default but reversible. - Handling Mismatches:
- If
p3270version doesn't match (e.g., !=0.3.0, as checked in patches), logs a warning and skips patches gracefully (no error unlessstrict_version=True). - If
p3270is not installed, patching simulates with mocks and logs a warning; use standalonepure3270.Sessioninstead. - Protocol differences: Pure3270 uses pure Python telnet/SSL, so ensure hosts support TN3270/TN3270E (RFC 1576/2355). SSL uses Python's
sslmodule.
- If
Before / After
Before (with s3270):
- Install
s3270binary. import p3270; session = p3270.P3270Client(); session.connect(...)(spawns s3270).
After (with pure3270):
- Install pure3270 as above.
import pure3270; pure3270.enable_replacement(); import p3270; session = p3270.P3270Client(); session.connect(...)(uses pure Python emulation).
Test migration by checking logs for "Patched Session ..." messages. For standalone scripts, switch to from pure3270 import Session.
Examples
See the examples/ directory for practical scripts:
example_patching.py: Demonstrates applying patches and verifying redirection.example_end_to_end.py: Full p3270 usage after patching (with mock host).example_standalone.py: Direct pure3270 usage without p3270.
Run them in your activated venv: python examples/example_patching.py. Replace mock hosts with real TN3270 servers (e.g., IBM z/OS systems) for production.
Troubleshooting
- Venv Activation Issues: Ensure the venv is activated (prompt shows
(.venv)). On Windows, useScripts\activate.bat. Ifpipinstalls globally, recreate the venv. - Patching Fails: Check logs for version mismatches (e.g.,
p3270!=0.3.0). Setstrict_version=Trueto raise errors. Ifp3270absent, use standalone mode. - Connection/Protocol Errors: Verify host/port (default 23/992 for SSL). Enable DEBUG logging:
pure3270.setup_logging('DEBUG'). Common: Host doesn't support TN3270; test with tools liketn3270client. - Screen Read Issues: Ensure
read()is called aftersend(). For empty screens, check if BIND negotiation succeeded (logs show). - Async/Sync Mix: Use
Sessionfor sync code;AsyncSessionfor async. Don't mix in the same script withoutasyncio.run().
For more, enable verbose logging or consult architecture.md.
Credits
Credits: Some tests and examples in this project are inspired by and adapted from the IBM s3270 terminal emulator project, which served as a valuable reference for 3270 protocol handling and emulation techniques.
License and Contributing
See setup.py for author info. Contributions welcome via issues/PRs.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pure3270-0.1.0b3.tar.gz.
File metadata
- Download URL: pure3270-0.1.0b3.tar.gz
- Upload date:
- Size: 37.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
837cabcd13899405964c3e1c04168874a7c406145a86f9ee9fa9fa2102ab18ea
|
|
| MD5 |
542f751989ef6ddf0f6e8fee917b0844
|
|
| BLAKE2b-256 |
1fa4e11a509a913fa1cfd83b991c35cb77acbe174fbf29b1a06a9d10b211e1dd
|
Provenance
The following attestation bundles were made for pure3270-0.1.0b3.tar.gz:
Publisher:
python-publish.yml on dtg01100/pure3270
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pure3270-0.1.0b3.tar.gz -
Subject digest:
837cabcd13899405964c3e1c04168874a7c406145a86f9ee9fa9fa2102ab18ea - Sigstore transparency entry: 485516114
- Sigstore integration time:
-
Permalink:
dtg01100/pure3270@0dcceeb5fb4a8b12045a8d77d726186cacbcb5d0 -
Branch / Tag:
refs/tags/0.1.0b3 - Owner: https://github.com/dtg01100
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
python-publish.yml@0dcceeb5fb4a8b12045a8d77d726186cacbcb5d0 -
Trigger Event:
release
-
Statement type:
File details
Details for the file pure3270-0.1.0b3-py3-none-any.whl.
File metadata
- Download URL: pure3270-0.1.0b3-py3-none-any.whl
- Upload date:
- Size: 41.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f07ca994b83b2651a35ed27c44b2adfde91b048a7d1419e7d04f1d8c9d2fb8b5
|
|
| MD5 |
0fcae3677a7b5a76c50c3b97e8a710f6
|
|
| BLAKE2b-256 |
d0ffff90483e320f901d6253ee386f7047b7531513a281dfe525560040a87510
|
Provenance
The following attestation bundles were made for pure3270-0.1.0b3-py3-none-any.whl:
Publisher:
python-publish.yml on dtg01100/pure3270
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pure3270-0.1.0b3-py3-none-any.whl -
Subject digest:
f07ca994b83b2651a35ed27c44b2adfde91b048a7d1419e7d04f1d8c9d2fb8b5 - Sigstore transparency entry: 485516133
- Sigstore integration time:
-
Permalink:
dtg01100/pure3270@0dcceeb5fb4a8b12045a8d77d726186cacbcb5d0 -
Branch / Tag:
refs/tags/0.1.0b3 - Owner: https://github.com/dtg01100
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
python-publish.yml@0dcceeb5fb4a8b12045a8d77d726186cacbcb5d0 -
Trigger Event:
release
-
Statement type: