Skip to main content

Guapy

CI PyPI Python License: MIT Code style: ruff

Embed browser-based remote desktops (RDP, VNC, SSH, Telnet) into your Python web application. Guapy is a framework-free asyncio library for the Guacamole protocol and its guacd daemon — no Java webapp, no Node.js sidecar.

It speaks the Guacamole wire protocol end to end: immutable instructions, an incremental bounded parser, version negotiation, the full guacd handshake, an asyncio TCP transport, and a mountable ASGI WebSocket tunnel for FastAPI, Starlette, and Django Channels.

Why Guapy?

Apache Guacamole renders remote desktops in the browser, driven by a small C daemon called guacd. To use it from a Python application you traditionally either run Guacamole's full Java webapp (its own auth model, its own database) or hand-write the protocol glue. Guapy is the missing Python layer:

Guapy guacamole-lite (Node) Apache Guacamole (Java)
Language Python Node.js Java
Async IO asyncio event loop servlet threads
Runs inside your app ✅ ASGI mount ✅ ❌ separate webapp
Per-connection authorization ✅ resolver you control encrypted token its own DB
Runtime dependencies zero ws, deep-extend servlet container
Typed exceptions with HTTP/WS mappings ✅ ❌ ✅

Guapy deliberately ships no authentication, token crypto, session registry, or database. Your app authorizes each tunnel through a one-method resolver; Guapy handles everything below that.

Quick start

Mount the tunnel in FastAPI and decide per request which remote host a user gets — the resolver sees the query string, headers (cookies!), and client address:

from fastapi import FastAPI
from guapy import (
    GuacamoleConfiguration,
    GuacamoleUnauthorizedError,
    GuacdEndpoint,
    QueryWhitelistResolver,
    ResolvedConnection,
    TunnelRequest,
)
from guapy.server import GuacamoleASGIApp

app = FastAPI()


class DashboardResolver:
    """Authorize tunnels with your own session machinery."""

    async def resolve(self, request: TunnelRequest) -> ResolvedConnection:
        session_id = request.headers.get("cookie", "")
        if not is_logged_in(session_id):  # your application logic
            raise GuacamoleUnauthorizedError("sign in first")
        vm = lookup_vm_for_user(session_id)  # your application logic
        return ResolvedConnection(
            GuacamoleConfiguration(
                protocol="rdp",
                parameters={
                    "hostname": vm.host,
                    "port": "3389",
                    "username": vm.username,
                    "password": vm.password,
                },
            )
        )


tunnel = GuacamoleASGIApp(
    endpoint=GuacdEndpoint(host="guacd.internal", port=4822),
    resolver=DashboardResolver(),
)
app.mount("/guacamole", tunnel)

Point guacamole-common-js in the browser at /guacamole/webSocket and the remote desktop renders.

Prefer the safe default? QueryWhitelistResolver wraps a fixed connection and lets clients override only what you whitelist (display size, color scheme…):

from guapy import QueryWhitelistResolver

resolver = QueryWhitelistResolver(
    GuacamoleConfiguration(
        protocol="ssh", parameters={"hostname": "bastion.internal", "port": "22"}
    ),
    allowed=("width", "height", "dpi", "color-scheme"),
)

Using the client directly

Guapy also works as a plain guacd client, no web layer involved:

import asyncio

from guapy import GuacamoleClient, GuacamoleConfiguration, GuacdEndpoint, Instruction


async def main() -> None:
    configuration = GuacamoleConfiguration(
        protocol="ssh",
        parameters={
            "hostname": "ssh.example.internal",
            "port": "22",
            "username": "alice",
            "password": "provided-by-your-application",
        },
    )

    client = GuacamoleClient()
    async with await client.connect(GuacdEndpoint(), configuration) as session:
        await session.send(Instruction.create("sync", "0"))
        instruction = await session.receive()
        if instruction is not None:
            print(instruction)


asyncio.run(main())

Applications remain responsible for which configuration is authorized. Never send connection credentials to an untrusted client or log them.

Features

  • Immutable Guacamole instructions and a bounded incremental parser that survives arbitrary TCP fragmentation and split UTF-8 code points.
  • Complete guacd handshake with protocol version negotiation, client capabilities (screen, audio/video/image formats, timezone, name), and support for joining existing sessions by connection ID.
  • Pluggable transports — asyncio TCP adapter included; TLS to guacd is a constructor flag. Bring your own connector for proxies or recording.
  • Ordered instruction filter pipelines for inspecting, rewriting, or dropping instructions on read and write paths.
  • Typed status model — every error carries its Guacamole status code and the mapped HTTP status and WebSocket close code, so tunnels close exactly the way the official Java server closes them.
  • Production-shaped ASGI tunnel: per-connection resolvers, connection limits, inactivity timeouts, open/close hooks, GET /health, ASGI lifespan support, and graceful drain on shutdown.

Guapy requires Python 3.10+ and installs with zero dependencies.

Try it in Docker

The e2e fixture is a complete Guacamole deployment in one command — Guapy, official guacd, a containerized SSH server (demo/demo), and a browser client:

docker compose -f e2e/docker-compose.yml up --build

Open http://localhost:9090, select New Connection → SSH → Connect. The same stack backs the integration tests:

GUAPY_TEST_GUACD_HOST=127.0.0.1 GUAPY_TEST_SSH_HOST=sshd uv run pytest tests/integration

CI runs exactly these tests against real guacd and sshd containers on every push — the handshake is verified against the actual daemon, not mocks.

Logging

Guapy uses the standard-library logger named guapy and never configures handlers or levels. It logs lifecycle events (tunnel open/close, protocol disconnects, guacd EOF) and never logs instructions or credentials.

Development

uv sync --dev
uv run pytest                 # unit tests (integration tests skip without guacd)
uv run ruff check .
uv run mypy src

See Architecture.md for the design, layering, and the boundaries Guapy deliberately does not cross. Contributions welcome — start with CONTRIBUTING.md.

License

MIT. See LICENSE.

Metadata

Release files for guapy 2.0.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for guapy 2.0.0
File Size Uploaded
guapy-2.0.0.tar.gz 169.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for guapy 2.0.0
File Interpreter ABI Platform
guapy-2.0.0-py3-none-any.whl Python 3 none any Details

Total release size: 198.9 kB

Release files / guapy-2.0.0.tar.gz

Download URL guapy-2.0.0.tar.gz
Size 169.2 kB
Tags Source
SHA-256 checksum
How to use checksums
7147220bd85e4fab93bade9a3ae35372c2f2f8cb143bfbe131ad131124db31fb
BLAKE2b-256 checksum
How to use checksums
7c2ff08988b58ec926b86d91a692a8e7e8d2004bfb00ae9012748486785eef27
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 1, 2026.

Transparency log

Release files / guapy-2.0.0-py3-none-any.whl

Download URL guapy-2.0.0-py3-none-any.whl
Size 29.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
9ceeca50b8e5adfdda083ee77b4b349320af6f323824d232b18a280d8b590e6b
BLAKE2b-256 checksum
How to use checksums
9ddd6189ba65f877f95242e977fc43c68d7a3f5e8db0c2c7712bc8e81369209c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 1, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

2.0.0 This release

2 release files

1.0.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page