This release is a pre-release and may not be stable for production use.
CrowdyPy
The official Python SDK for Crowded Kingdoms: typed clients for the platform's one GraphQL API (identity, studio administration, world data, ck-exec) and a native UDP replication core.
CrowdyPy follows the CrowdyJS API surface
(the same domains, the same two-token model, the same error codes) the way
CrowdyCPP does, with Python names
(mintAppToken is mint_app_token). Its realtime path is CrowdyCPP's native replication
client, bound with nanobind and shipped inside the
wheel. Python never touches a datagram.
v0.4.0: headless Crowdy Studio, and full parity with CrowdyJS.
crowdypy.studio.CrowdyStudioControlleris the Studio editor without a browser: open projects, edit with autosave and revision conflicts, bind GitHub, apply agent patches with checkpoints, and build, deploy and run mods.StudioLayoutControllerandparse_rustc_diagnosticsmatch CrowdyJS byte for byte.- The roots CrowdyCPP sends and CrowdyJS does not are wrapped too: email confirmation, usage
projections, compute budgets, listing versions, the narrow Studio mutations, and
client.crowdy_studio_agent. crowdypy.player_hostis the typed observation contract a game implements so tooling can read the player it controls, validated exactly as CrowdyJS validates it.- The parity gate is strict:
docs/parity-matrix.mdhas no gaps. - 0.3.0 brought the World Stores, the Game Kit, ck-exec and subscriptions; 0.2.0 native UDP
replication (
client.udp); 0.1.0 the GraphQL client.
See MIGRATION.md.
Install
pip install crowdypy
Wheels: CPython 3.12+ (one abi3 wheel per platform) and free-threaded CPython 3.14, on
Linux x86_64 / aarch64, macOS arm64 / x86_64 and Windows x64. OpenSSL is linked statically;
nothing else native is needed. docs/compatibility.md has the
details, including the tier versions below.
Each tier of the platform has its own release line, and a build defaults to its own tier's
API origin (crowdypy.CROWDY_DEFAULT_TIER says which):
| Tier | PyPI version | Pin |
|---|---|---|
| dev | X.Y.Z.devN |
crowdypy==0.1.0.dev1 |
| test | X.Y.ZrcN |
crowdypy==0.1.0rc1 |
| prod | X.Y.Z |
crowdypy==0.1.0 |
pip skips pre-releases unless a requirement names one, so a prod range never picks up a dev or test build. The same wheels are attached to each GitHub release.
Quick start
import asyncio
import crowdypy
API = "https://ck.dev.crowdedkingdoms.com"
async def main() -> None:
# The identity client: sign-in, account and studio calls, minting.
async with crowdypy.AsyncCrowdyClient(http_url=API) as identity:
await identity.auth.login("player@example.com", "correct-horse-battery")
minted = await identity.portal.mint_app_token("42")
# The game client: one per app, at the app's own datacenter.
async with crowdypy.AsyncCrowdyClient(
http_url=minted.game_api_url or API, discovery_url=minted.discovery_url
) as game:
game.set_app_token(minted)
print(await game.server_status.server_with_least_clients())
asyncio.run(main())
Without an event loop, crowdypy.sync.CrowdyClient has the same methods, without await:
from crowdypy.sync import CrowdyClient
with CrowdyClient(http_url=API) as identity:
identity.auth.login("player@example.com", "correct-horse-battery")
print(identity.users.me())
Two tokens, two clients
There is one API origin but two tokens, as in every Crowded Kingdoms SDK:
- Sign-in (
auth.login, a magic link, or social/OIDC) yields an identity session token: account, studio administration and minting. It is not accepted for gameplay. - Gameplay needs a short-lived app-scoped token per app
(
portal.mint_app_token(app_id)), which is also the HMAC key for native UDP. Refresh it withclient.refresh_gameplay_token()(concurrent callers share one refresh). - Build one identity client and one client per game. The game client points at the app's
datacenter (
game_api_url) withdiscovery_urlset, so it can find the app again if that instance stops answering.
Persist them the same way: FileTokenStore.session_path(directory, api_origin) names the
session file (one per origin, shared by every game) and FileTokenStore.app_path(directory, app_id) the gameplay one (one per app):
from crowdypy import FileTokenStore
identity = crowdypy.AsyncCrowdyClient(
http_url=API, token_store=FileTokenStore(FileTokenStore.session_path(state_dir, API))
)
CrowdyPy sends no Origin header, so direct sign-in is served to it; hosted browser
sign-in (portal.signIn in CrowdyJS) is a browser flow and is not part of this SDK.
Errors
Every error is a crowdypy.CrowdyError. A GraphQL refusal is a CrowdyGraphQLError with the
server's stable code (error.code == "FORBIDDEN") and extensions; HTTP, network and
timeout failures have their own classes. CrowdyAppUnavailableError (the app's datacenter
is not serving) and CrowdyUserCodeFaultError (a player module faulted;
player_fault_of(error) reads the fault) mirror CrowdyJS. A WRONG_DATACENTER refusal
moves the client to the app's datacenter and retries once, by itself.
Replication
minted = await identity.portal.mint_app_token("42")
async with crowdypy.AsyncCrowdyClient(
http_url=minted.game_api_url or API, discovery_url=minted.discovery_url
) as game:
await game.udp.connect(minted) # assign a server, open the socket
game.udp.subscribe({"actor_update": lambda n: print(n.uuid, n.chunk, n.payload)})
await game.udp.send_actor_update((0, 0, 0), my_uuid, pose_bytes)
echo = await game.udp.send_actor_update_and_wait((0, 0, 0), my_uuid, pose_bytes)
For a game loop, use the connection underneath: send a frame's entities in one call, and read whole batches.
conn = game.udp.connection
conn.send_actor_updates(chunks, uuids, poses, stride=88) # numpy (n, 3) int64, n x 32 octets
async for batch in conn.batches():
actors = batch.types == crowdypy.wire.MessageType.ACTOR_UPDATE_NOTIFICATION
positions = batch.chunks[actors] # a view, not a copy
crowdypy.replication.ReplicationConnection is the same for code without an event loop:
wait() and then poll() from your own loop.
World Stores
from crowdypy.codecs import f32, struct_codec, u16
from crowdypy.stores import create_world_session
pose = struct_codec({"x": f32(), "y": f32(), "z": f32(), "yaw": u16()})
session = create_world_session(game, app_id, actor_codec=pose, host=True, save=True)
session.self.join((0, 0, 0), {"x": 0.0, "y": 64.0, "z": 0.0, "yaw": 0})
session.actors.on_join(lambda actor: print("joined", actor.uuid, actor.value))
asyncio.create_task(session.run()) # tick 60 times a second
snapshot = session.actors.snapshot() # once a frame: every actor at once
states = pose.decode_many(snapshot.state_offsets, snapshot.state_data) # numpy, no loop
Crowdy Studio
from crowdypy.studio import CrowdyStudioController
studio = CrowdyStudioController.for_client(game, app_id=app_id, grid_id=grid_id)
await studio.initialize() # opens the first project
studio.update_file("SERVER", "src/lib.rs", source) # autosaves
result = await studio.test_draft() # build, deploy and switch on the mod
print(result.status, studio.get_state().build_output)
A CLIENT target runs in a host you pass as broker_factory; the browser Studio runs it in a
Web Worker.
Performance
Python does no work per datagram. Receive, HMAC verification and decoding happen on
CrowdyCPP's network thread, which never takes the GIL; Python sees one batch object per
poll. Sends release the GIL, and a batch releases it once for the whole batch. An asyncio
loop is woken through a socket, not by polling. Measured against CrowdyCPP on the same
machine (benchmarks/README.md): a 200-entity batch costs 1.06x
CrowdyCPP's own send path per entity, Python takes in two million verified notifications a
second, a notification reaches an asyncio handler 47 µs after it hits the wire (p50), and a
60 Hz loop stays within 0.2 ms of its schedule while receiving 60,000 a second.
Development
uv sync # builds the extension (CMake, a C++20 compiler) and installs dev tools
uv run pytest tests/unit
uv run ruff check . && uv run ruff format --check . && uv run mypy
The live suites in tests/e2e/ run against a deployment when the CROWDY_E2E_* variables
are set; docs/e2e-coverage.md lists them, how to configure them
and the last live runs.
The gates CI runs, all from a clean checkout:
| Gate | Command |
|---|---|
| Vendored CrowdyCPP is the pinned commit's tree | python scripts/vendor_crowdycpp.py --check |
| schema.gql and operations are CrowdyJS's at the pin | python scripts/schema_sync.py --check |
| Generated code is current | python scripts/codegen.py --check |
| The blocking client is generated from the async one | python scripts/unasync.py --check |
| Parity with CrowdyJS | python tools/parity/parity.py --crowdyjs ../CrowdyJS --check docs/parity-matrix.md |
| No internal names in the tree or the artifacts | python scripts/ci/check_content_policy.py |
AGENTS.md covers maintenance: re-pinning CrowdyJS and CrowdyCPP, the tier branches and the release path.
License
MIT. Wheels also contain CrowdyCPP (MIT), yyjson (MIT) and OpenSSL (Apache-2.0).
Metadata
Release files for crowdypy 0.4.0.dev1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| crowdypy-0.4.0.dev1.tar.gz | 1.1 MB | Details |
Built distributions (wheels)
Total release size: 39.5 MB
Release files / crowdypy-0.4.0.dev1.tar.gz
| Download URL | crowdypy-0.4.0.dev1.tar.gz |
|---|---|
| Size | 1.1 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
116fc8f42a451f4b15ee81ff0f90c0b208106c03c0928d446a58f1522221812e
|
|
BLAKE2b-256 checksum How to use checksums |
1bd770ee513f8e8e7ac5eb32f9c9871598a3fe69396d84c3de191b93cd3aca77
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.14
|
Release files / crowdypy-0.4.0.dev1-cp314-cp314t-win_amd64.whl
| Download URL | crowdypy-0.4.0.dev1-cp314-cp314t-win_amd64.whl |
|---|---|
| Size | 2.4 MB |
| Tags | CPython 3.14 CPython 3.14 free-threading Windows x86-64 |
|
SHA-256 checksum How to use checksums |
345c055cd0033eb8a007fb6e629721b0c5dfe3df4a013fee6dfb94cb360db85b
|
|
BLAKE2b-256 checksum How to use checksums |
e375310c01ade3902b6c83f72d8bc16904535a1a64a1a05b87c64141e9e20b23
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.14
|
Release files / crowdypy-0.4.0.dev1-cp314-cp314t-musllinux_1_2_x86_64.whl
| Download URL | crowdypy-0.4.0.dev1-cp314-cp314t-musllinux_1_2_x86_64.whl |
|---|---|
| Size | 2.8 MB |
| Tags | CPython 3.14 CPython 3.14 free-threading Linux musl 1.2+ x86-64 |
|
SHA-256 checksum How to use checksums |
9b41ec9bd144207faca4b5eaa5de9fbf37657500304cfd8da6b164ad6ebc6f82
|
|
BLAKE2b-256 checksum How to use checksums |
a421578bc1644c90a97187d66fd89af7a100df431326e9f40c53b13f68682525
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.14
|
Release files / crowdypy-0.4.0.dev1-cp314-cp314t-musllinux_1_2_aarch64.whl
| Download URL | crowdypy-0.4.0.dev1-cp314-cp314t-musllinux_1_2_aarch64.whl |
|---|---|
| Size | 3.1 MB |
| Tags | CPython 3.14 CPython 3.14 free-threading Linux musl 1.2+ ARM64 |
|
SHA-256 checksum How to use checksums |
6bb6ccc1c5520c981178c45af4a63227e0f0aa38a3ab6cf6f77c299152a8cfbf
|
|
BLAKE2b-256 checksum How to use checksums |
054d55fff1a1ec577d2bd80e990ab855be903e173fe43f07ade26a6da2a73fbc
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.14
|
Release files / crowdypy-0.4.0.dev1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
| Download URL | crowdypy-0.4.0.dev1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl |
|---|---|
| Size | 2.7 MB |
| Tags | CPython 3.14 CPython 3.14 free-threading Linux glibc 2.27+ x86-64 Linux glibc 2.28+ x86-64 |
|
SHA-256 checksum How to use checksums |
15687cbb7c1ff6081934d9ca3bb2aef7879b17a93e42906c0c67d9c131ac09e6
|
|
BLAKE2b-256 checksum How to use checksums |
c13988b0f7a03b2eb24b61fd466ddded2097c58f6b5c688fe499f7822e8ae20e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.14
|
Release files / crowdypy-0.4.0.dev1-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl
| Download URL | crowdypy-0.4.0.dev1-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl |
|---|---|
| Size | 3.0 MB |
| Tags | CPython 3.14 CPython 3.14 free-threading Linux glibc 2.26+ ARM64 Linux glibc 2.28+ ARM64 |
|
SHA-256 checksum How to use checksums |
d96ed68629a6c4e06947791e650207b316bc81d2396188230ca0ee64cec35a91
|
|
BLAKE2b-256 checksum How to use checksums |
f0465e178e20df5528e8311a6ebb3c954932082b8fc8b76ae72d1a927430df5b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.14
|
Release files / crowdypy-0.4.0.dev1-cp314-cp314t-macosx_11_0_x86_64.whl
| Download URL | crowdypy-0.4.0.dev1-cp314-cp314t-macosx_11_0_x86_64.whl |
|---|---|
| Size | 2.4 MB |
| Tags | CPython 3.14 CPython 3.14 free-threading macOS 11.0+ x86-64 |
|
SHA-256 checksum How to use checksums |
7f0902d4ab9c6e1709bf4ee43023a233b8e466aa903cd0832a6393e0dfb01051
|
|
BLAKE2b-256 checksum How to use checksums |
73994b041d13c491d0ff1be40414172fdea623c00047e84b7f222e53c4e2bf1d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.14
|
Release files / crowdypy-0.4.0.dev1-cp314-cp314t-macosx_11_0_arm64.whl
| Download URL | crowdypy-0.4.0.dev1-cp314-cp314t-macosx_11_0_arm64.whl |
|---|---|
| Size | 2.7 MB |
| Tags | CPython 3.14 CPython 3.14 free-threading macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
57eafb4e3e38b3b4cb25fa50428666cdd9bab10f66ad7d017a063626e2764af6
|
|
BLAKE2b-256 checksum How to use checksums |
c98ccb3a744220a5a64d507f5ba9b31f8e995edd11d42702862a4ce2f945e5db
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.14
|
Release files / crowdypy-0.4.0.dev1-cp312-abi3-win_amd64.whl
| Download URL | crowdypy-0.4.0.dev1-cp312-abi3-win_amd64.whl |
|---|---|
| Size | 2.4 MB |
| Tags | CPython 3.12 Windows x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
eade5f9737da901b3db051fcc8f6a4230a9e7da8c86988421b55b789cb076aa2
|
|
BLAKE2b-256 checksum How to use checksums |
5b133d80d5c88db4b9d248f10099579ea0a80eb18fce6c830de197bc87cec633
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.14
|
Release files / crowdypy-0.4.0.dev1-cp312-abi3-musllinux_1_2_x86_64.whl
| Download URL | crowdypy-0.4.0.dev1-cp312-abi3-musllinux_1_2_x86_64.whl |
|---|---|
| Size | 2.8 MB |
| Tags | CPython 3.12 Linux musl 1.2+ x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
e577c027c84449450353cd274d6f6f44b74f174397a3fb6621211d6263b71a0f
|
|
BLAKE2b-256 checksum How to use checksums |
0f57868458f940f1d1f0e8fe9534e0492bdb89eb85a87041387b045273697de7
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.14
|
Release files / crowdypy-0.4.0.dev1-cp312-abi3-musllinux_1_2_aarch64.whl
| Download URL | crowdypy-0.4.0.dev1-cp312-abi3-musllinux_1_2_aarch64.whl |
|---|---|
| Size | 3.1 MB |
| Tags | CPython 3.12 Linux musl 1.2+ ARM64 abi3 |
|
SHA-256 checksum How to use checksums |
3ad65016cbece32d7d528cfe0ef641735d8950455bad4e238737baa0c7c3951e
|
|
BLAKE2b-256 checksum How to use checksums |
5fa32dd5ecf3f166f173de26eb16d327f058a684a8e918000750a10d57a585c1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.14
|
Release files / crowdypy-0.4.0.dev1-cp312-abi3-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
| Download URL | crowdypy-0.4.0.dev1-cp312-abi3-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl |
|---|---|
| Size | 2.7 MB |
| Tags | CPython 3.12 Linux glibc 2.27+ x86-64 Linux glibc 2.28+ x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
560834e5262b78fe15ea70b94a347abdaca6cf01a791539c8d3d0dbab45b37c3
|
|
BLAKE2b-256 checksum How to use checksums |
2ef651d6678376e20454f581ed97bf42126342e18513068e89d678c566be00d6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.14
|
Release files / crowdypy-0.4.0.dev1-cp312-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl
| Download URL | crowdypy-0.4.0.dev1-cp312-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl |
|---|---|
| Size | 3.0 MB |
| Tags | CPython 3.12 Linux glibc 2.26+ ARM64 Linux glibc 2.28+ ARM64 abi3 |
|
SHA-256 checksum How to use checksums |
a7522d447da8540457c4256b4188edc530c3c11ad2f697d72ec24ad261d4ca0a
|
|
BLAKE2b-256 checksum How to use checksums |
37897db038fd278158ce5920e0eea69e8e3e35ea845ece0260b9a94ef369e2e6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.14
|
Release files / crowdypy-0.4.0.dev1-cp312-abi3-macosx_11_0_x86_64.whl
| Download URL | crowdypy-0.4.0.dev1-cp312-abi3-macosx_11_0_x86_64.whl |
|---|---|
| Size | 2.4 MB |
| Tags | CPython 3.12 abi3 macOS 11.0+ x86-64 |
|
SHA-256 checksum How to use checksums |
31685d1855c4dfb3f23ca27f740f0911b7d3c090e0029f547a42b624fc8e1c63
|
|
BLAKE2b-256 checksum How to use checksums |
d1b7f99edc86fd83fd6673088e72076d0482cfd1829c8128c11ca8b6c2476573
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.14
|
Release files / crowdypy-0.4.0.dev1-cp312-abi3-macosx_11_0_arm64.whl
| Download URL | crowdypy-0.4.0.dev1-cp312-abi3-macosx_11_0_arm64.whl |
|---|---|
| Size | 2.7 MB |
| Tags | CPython 3.12 abi3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
5ba9882b55915796419c1e41d5aac8bcb1361f6a1138fd3c31a64c6d391e1dfd
|
|
BLAKE2b-256 checksum How to use checksums |
9827ec36d86a36ddf2e0c963be90f84a40a90394ca744697cdbbb839111cb419
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.14
|