Skip to content

Getting Started

This guide introduces the primary components of Port70 and demonstrates how to execute Gopher requests, parse URIs, and handle errors.

All of the public classes, constants, and exceptions are exposed at the top level of the package. You can import them directly from port70.

Core Components

The following classes form the core interface of the library:

  • Client: The asynchronous client used to configure and dispatch requests over TCP port 70.
  • Response: Represents the server's response, exposing the target URI (uri), item type (item_type), decoded response text (text), response lines (lines), raw bytes (raw_bytes), and content helpers (is_text).
  • GopherURI: A utility class to parse, validate, and manipulate Gopher URIs and target strings safely.
  • Port70Error: The base exception class for all errors raised by the library.

Basic Request

To execute a request, initialise a Client and call its request method. The client automatically resolves target host details and executes the network connection:

import asyncio
from port70 import Client, Port70Error

async def main():
    async with Client() as client:
        try:
            # Execute the request (accepts a URI string or a GopherURI)
            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 Body ---")
            print(response.text)

        except Port70Error as error:
            print(f"Request failed: {error}")

if __name__ == "__main__":
    asyncio.run(main())

Working with GopherURI

The GopherURI class allows you to parse both full URI strings (gopher://gopher.floodgap.com/7/v2/vs?python) and target format strings (gopher.floodgap.com):

from port70 import GopherURI

# Parse from a 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

# GopherURI objects are immutable; construct new instances with updated parts
custom_port_uri = uri.with_port(7070)
parent_uri = uri.parent
root_uri = uri.root

Search Queries and Item Types

RFC 1436 supports search queries for item type 7 (search engine) items. You can pass search terms directly to the client's request method or inspect the response's item type:

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",
    )

    # Check if response item type is text-based (types '0', '1', '7', 'h')
    if response.is_text:
        # Access text response split into lines (stripping trailing Gopher period)
        for line in response.lines[:5]:
            print(line)

Exception Hierarchy

All exceptions raised by the library inherit from the base class Port70Error. When managing errors, you can catch specific subclasses for finer control:

  • URIError: Raised when a given URI or target string cannot be parsed or validated.
  • ConnectionError: Raised when network connections to the Gopher server fail or are refused.
  • TimeoutError: Raised when a Gopher query operation times out.
  • ProtocolError: Raised when a Gopher protocol error occurs.
  • ResponseError: Raised when processing or parsing response content fails.