Port70 is an async-first, fully type-hinted Python client library for the Gopher protocol (RFC 1436 / RFC 4266).
- Async First: Built on top of Python's standard
asyncionetworking loop. - RFC 1436 & RFC 4266 Support: Full support for standard Gopher item types, selectors, search queries, and URIs.
- Type Safe: Fully typed API passing strict static type checking (
mypy --strict). - GopherURI Representation: Rich URI class to parse, inspect, validate, manipulate, and resolve Gopher URIs and query targets.
- Zero Dependencies: Built entirely on Python's standard library.
- CLI Utility: Includes a
port70command-line interface out of the box.
port70 requires Python 3.12 or later and can be installed with your package manager of choice.
With pip:
pip install port70With uv:
uv add port70Use Client with standard async context managers to query a Gopher server:
import asyncio
from port70 import Client, Port70Error
async def main():
async with Client() as client:
try:
# Query a Gopher menu or document
response = await client.request("gopher://gopher.floodgap.com/")
print(f"Target host: {response.uri.host}")
print(f"Item type: {response.item_type}")
print(f"Content length: {len(response.content)} bytes")
print("--- Response Text ---")
print(response.text)
except Port70Error as error:
print(f"Request failed: {error}")
if __name__ == "__main__":
asyncio.run(main())The GopherURI class parses both full URI strings (gopher://host/1/selector) and target format strings:
from port70 import GopherURI
# Parse from Gopher URI string
uri = GopherURI.from_string("gopher://gopher.floodgap.com/7/v2/vs?python")
print(uri.host) # 'gopher.floodgap.com'
print(uri.port) # 70
print(uri.item_type) # '7'
print(uri.selector) # '/v2/vs'
print(uri.query) # 'python'
print(uri.is_search) # True
# Create modified copies
custom_port_uri = uri.with_port(7070)
parent_uri = uri.parent
root_uri = uri.rootYou can issue search queries for item type 7 search engines or inspect item types on responses:
async with Client() as client:
# Send a search request with query terms
response = await client.request(
"gopher://gopher.floodgap.com/7/v2/vs",
query_terms="python",
)
assert response.is_text
# Access text response split into lines
for line in response.lines[:5]:
print(line)port70 includes a command-line client:
# Query a Gopher target (defaults to gopher://gopher.floodgap.com/)
uv run port70 gopher://gopher.floodgap.com/
# Perform a search request
uv run port70 -q python gopher://gopher.floodgap.com/7/v2/vs
# Query on a custom port
uv run port70 -p 7070 gopher://example.com/1/
# Output raw binary content to stdout
uv run port70 -r gopher://gopher.floodgap.com/0/gopher/readdata
# Display CLI help
uv run port70 --helpMIT