Skip to main content

pookiepy

pookiepy is a Python framework for building asynchronous gRPC bidirectional-streaming services. Subclass BaseServer and BaseClient, override the hooks you need --- the framework handles all gRPC plumbing.

PyPI Python License

Status: Work in Progress. The project is open source and will remain open source. Treat with caution. If you depend on it, pin your version. Semantic versioning will only begin with the first official release, starting at version 1.0.0. Note that until and including version 0.0.11 the project was named grpchook.


Table of Contents


Disclaimer

Core architecture and design were created by a human developer. AI was used extensively for unit and integration test creation, examples, documentation, refinements, and selected code sections. Core logic was human-reviewed, but the full test suite has not been fully audited --- AI-introduced oversights may still exist. Please report any issues you find.

This software is provided "as is", without warranty of any kind. The developer is not responsible for any damage, data loss, security vulnerabilities, or other issues that may arise from using this software. You use it at your own risk. See LICENSE.txt for the full BSD 3-Clause terms.


When to Use and When Not to Use pookiepy

When to Use pookiepy

  • You need a simple, Python-based gRPC bidirectional streaming server and client.

  • You want a data exchange blueprint for developers or AI agents to build on top of.

  • You want a framework that can be extended with custom hooks for specific events.

  • You want to distribute clients to many different machines (e.g. voice recorder, voice to text, text to LLM, and vice versa until the final response is replayed)

    Example --- four clients on four machines, all routed through one pookiepy server:

    💡 Diagram requires the Markdown Preview Mermaid Support extension to render in VS Code.

    flowchart LR
        subgraph M1["📦 Machine 1"]
            VR["🎤 Voice Recorder"]
        end
        subgraph M2["📦 Machine 2"]
            STT["📝 Speech-to-Text"]
        end
        subgraph M3["📦 Machine 3"]
            LLM["🤖 LLM Processor"]
        end
        subgraph M4["📦 Machine 4"]
            RP["🔊 Voice Replay"]
        end
    
        SRV(["⚙️ pookiepy Server"])
    
        VR  -->|"① audio"| SRV
        SRV -->|"① audio"| STT
        STT -->|"② transcript"| SRV
        SRV -->|"② transcript"| LLM
        LLM -->|"③ llm_response"| SRV
        SRV -->|"③ llm_response"| RP
    

When Not to Use pookiepy

  • When you need a very large number of clients; the threading model may introduce overhead.
  • When you need direct peer-to-peer communication without a server intermediary; pookiepy routes all messages through a central server.
  • You want a framework that supports multiple programming languages out of the box; pookiepy is (currently) Python-only.

Requirements

  • Python 3.10 or later
  • A dedicated virtual environment is strongly recommended --- gRPC version conflicts with other packages are common when using pookiepy.

Installation

From PyPI

pip install pookiepy

From Source

git clone https://github.com/fwkrumm/pookiepy.git
cd pookiepy
pip install -e .

Quick Start

Refer to HOW_TO.md for the full API reference and code examples. Alternatively run

python -m pookiepy --generate-skeletons

to generate a very basic server and client skeleton in the current directory. Use

python -m pookiepy --generate-interface-with-skeletons

to generate the skeletons along with a copy of the message.proto interface file in the current directory to modify which is then used by the skeletons.

Custom protobuf interface

Pookiepy accepts precompiled protobuf modules through one explicit interface object. Compile the schema outside pookiepy, import both generated modules, then inject the same object into each matching client and server:

from my_proto import message_pb2, message_pb2_grpc
from pookiepy.custom_interface import ProtoInterface

proto_interface = ProtoInterface(message_pb2, message_pb2_grpc)
server = MyServer(port=50051, proto_interface=proto_interface)
client = MyClient(port=50051, proto_interface=proto_interface)

Pookiepy performs no runtime compilation or global module registration. Omitting proto_interface uses the bundled schema. Custom client and server schemas must be wire-compatible.


Parameters

You can print the following text via python -m pookiepy --help:

usage: python -m pookiepy [-h] [--generate] [--generate-skeletons] [--generate-server] [--generate-client] [--generate-how-to] [--generate-interface] [--generate-interface-with-skeletons]

pookiepy scaffolding tool.

Generates skeleton server/client files and the HOW_TO reference
document into the current working directory.

options:
  -h, --help            show this help message and exit
  --generate            Generate server_skeleton.py, client_skeleton.py, and HOW_TO.md
  --generate-skeletons  Generate server_skeleton.py and client_skeleton.py
  --generate-server     Generate server_skeleton.py only
  --generate-client     Generate client_skeleton.py only
  --generate-how-to     Copy HOW_TO.md into the current directory
  --generate-interface  Copy message.proto into the current directory and print customisation instructions
  --generate-interface-with-skeletons
                        Copy message.proto and write server_skeleton.py + client_skeleton.py that inject precompiled custom protobuf modules

examples:
  python -m pookiepy --generate                          # skeleton + HOW_TO
  python -m pookiepy --generate-skeletons                  # server + client only
  python -m pookiepy --generate-server                   # server only
  python -m pookiepy --generate-client                   # client only
  python -m pookiepy --generate-how-to                   # HOW_TO.md only
  python -m pookiepy --generate-interface                # message.proto + instructions
  python -m pookiepy --generate-interface-with-skeletons  # proto + matching skeletons

Minimal Examples

Ultra-minimal --- no subclassing required

The simplest possible working setup: start a server, connect two clients, exchange a message. Everything runs in a single script --- no subclassing or hook overrides needed.

# example_minimal.py
import threading
from pookiepy.baseserver import BaseServer
from pookiepy.baseclient import BaseClient
from pookiepy.tools import generate_message

# start the server in a background thread
server = BaseServer(port=50051, name="server")
threading.Thread(target=server.serve_forever, daemon=True).start()

# both clients declare the same channel name
# fan-out skips the sender, so client_b receives what client_a sends
client_a = BaseClient(port=50051, name="A", provides=["ping"], requires=["ping"])
client_b = BaseClient(port=50051, name="B", provides=["ping"], requires=["ping"])

client_a.send_data(generate_message("ping", byte_payload=b"hello"))

msg = client_b.get_data(timeout=5.0)
client_a.logger.info(msg.payload.bytePayload)   # b"hello"
client_b.logger.info(msg.payload.bytePayload)   # b"hello"

client_a.disconnect()
client_b.disconnect()
server.shutdown()

Request / response --- subclass with hooks

For real workloads, subclass BaseServer to control routing and BaseClient to react to messages via the on_receive hook.

Design note (important): request/response in pookiepy is intentionally minimalistic. There is no dedicated request() helper in the core API by default; correlation is done via messageId and responseToId in normal hook/polling flow. This keeps the framework lean, transparent, and robust for mixed traffic patterns.

Need full reference flow?

  • tests/integration/request_response/server_request_response.py
  • tests/integration/request_response/clients_request_response.py

server.py

from pookiepy.baseserver import BaseServer, Peer
from pookiepy.tools import generate_message
import pookiepy.message_pb2 as pb2


class EchoServer(BaseServer):
    def __init__(self):
        super().__init__(port=50051, name="echo-server")

    def on_receive(self, peer: Peer, request: pb2.PookieMessage) -> bool:
        if request.metaInfo.messageName == "request":
            reply = generate_message("response", byte_payload=request.payload.bytePayload)
            self._data_register.add_data_for_message_name(
                peer.client_id, "response", reply,
                target_client_id=peer.client_id,   # unicast back to sender
            )
            return False   # skip default fan-out; routing handled above
        return True


EchoServer().serve_forever()

client.py

from pookiepy.baseclient import BaseClient
from pookiepy.tools import generate_message
from pookiepy.exceptions import ClientExit, GrpcEmpty
import pookiepy.message_pb2 as pb2


class EchoClient(BaseClient):
    def __init__(self):
        super().__init__(port=50051, name="echo-client",
                         provides=["request"], requires=["response"])

    def on_receive(self, data: pb2.PookieMessage):
        print(f"Server replied: {data.payload.bytePayload.decode()}")


client = EchoClient()
client.send_data(generate_message("request", byte_payload=b"hello, pookiepy!"))
try:
    client.spin(timeout=5.0)   # calls on_receive() per message
except (ClientExit, GrpcEmpty):
    pass   # timeout/disconnect
client.disconnect()

Run the server first, then the client:

# terminal 1
python server.py

# terminal 2
python client.py

Examples

Runnable examples are available in two locations:

  • examples/ --- self-contained, scenario-focused examples
  • tests/integration/ --- integration test scenarios covering a broad range of use cases

Run them on a machine with adequate resources; some scenarios are resource-intensive.


Testing

Install dev dependencies and run the unit tests:

pip install -r requirements_dev.txt
python -m unittest discover -s tests

Integration tests are in tests/integration/ and can be run via:

python tests/integration/run_integration_tests.py

Extend default Configuration

Example for a client to use the default configuration but disable proxy forwarding:

from pookiepy.baseclient import BaseClient, ClientConfig

class TestClient(BaseClient):
    def __init__(self):
        config = ClientConfig()
        config.grpc_options += [("grpc.enable_http_proxy", 0)]
        super().__init__(port=50051, name="test-client", config=config)

Regenerating the gRPC Interface

If you modify pookiepy/message.proto after cloning the repository, regenerate the Python bindings with:

python -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. --pyi_out=. pookiepy/message.proto

Note that all clients which connect to a server should use the same proto layout. For runtime checking, set matching manual schema labels on both sides, for example ClientConfig(schema_version="chat-v1") and ServerConfig(schema_version="chat-v1"). If both values stay empty, the connection is allowed and the server logs a warning that schema validation could not run. The different signals for the clients must be used in substructures:

message Payload {
    // For client A
    SomeTypeA payloadClientA = 1;

	// For client B
    SomeTypeB payloadClientB = 2;

    ...
}

ToDos and Roadmap

Performance & Stability

  • Evaluate replacing the threading model with asyncio if the performance gain justifies the API tradeoff.
  • Verify behavior when connections are interrupted mid-stream; ensure no ghost threads or queue deadlocks occur.

Planned Features

  • Multi-language client example (e.g., C++ or Rust).

Known Issues and Troubleshooting

TBD


Contributing

Contributions are welcome. Please open an issue first for major changes so the approach can be discussed. For bug fixes and small improvements, a pull request is sufficient.


Compatibility Adjustments

Version-specific migration notes for breaking API and behavior changes are maintained in docs/required_adjustments/. See the 0.0.16 adjustment notes when upgrading from 0.0.15.


License

BSD 3-Clause --- see LICENSE.txt.


Release History

Version / Git Tag on Master Description
0.0.1 Unpublished.
0.0.2 Initial public release.
0.0.3 Add ms timestamp resolution to log output and minor adjustments to readme.
0.0.4 Add executor for server and wait for shutdown.
0.0.5 Fix readme on pypi page.
0.0.6 Update how-to markdown to include custom interface description.
0.0.7 Fix race condition which allowed clients to put data before welcome message.
0.0.8 Fix link in readme for pypi page.
0.0.9 Minor performance adjustments, adding compression parameter, changing logging parameters.
0.0.10 Add on_data_yield hook, added responseToId field, more explicit logging for startup.
0.0.11 Add deprecation warning because of project rename.
0.0.12 Project renamed to pookiepy.
0.0.13 Revert publish via token and add note concerning old project name.
0.0.14 Change schema version to manual setting because of incompatibilities between different languages.
0.0.15 Add exception for custom interface mismatch, add __version__ to __all__.
0.0.16 Change client spin control flow and server delivery-result APIs; add queue-growth configuration.
0.0.17 Rename non-ambiguous message name. Simplify custom interface usage. Minor improvements to logs and comments. added voice client to interactive streaming example. added workflow for skeletons. added threading backend for timer.
0.0.18 Correct on_init position, hopefully fix publish pipeline, correct on_receive hook position.

Metadata

Release files for pookiepy 0.0.18

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

Source distribution (sdist)

Source distribution for pookiepy 0.0.18
File Size Uploaded
pookiepy-0.0.18.tar.gz 147.1 kB Details

Built distribution (wheel)

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

Total release size: 248.8 kB

Release files / pookiepy-0.0.18.tar.gz

Download URL pookiepy-0.0.18.tar.gz
Size 147.1 kB
Tags Source
SHA-256 checksum
How to use checksums
a9ded54cd031be0f9bd3e2f30034e327508ae6a1cee522eb474350b53f58641d
BLAKE2b-256 checksum
How to use checksums
c9eae2c88925064f22b0b010563c59189a647ffdaa7de3d3ed795c66cb005726
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 14, 2026.

Transparency log

Release files / pookiepy-0.0.18-py3-none-any.whl

Download URL pookiepy-0.0.18-py3-none-any.whl
Size 101.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
49c3b22b520eedce927ce777d583b2dfee896eff60c4ae22e133f6a32e092ad1
BLAKE2b-256 checksum
How to use checksums
ed9607b9a2ee034da9223135fd740a27d4a48ec49f055bb7107e4f7da7846948
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 14, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.0.18 This release

2 release files

0.0.17

2 release files

0.0.16

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