Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions examples/chat-room-python/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.actorcore
node_modules
35 changes: 35 additions & 0 deletions examples/chat-room-python/actors/app.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { actor, setup } from "actor-core";

// state managed by the actor
export interface State {
messages: { username: string; message: string }[];
}

export const chatRoom = actor({
// initialize state
state: { messages: [] } as State,

// define actions
actions: {
// receive an action call from the client
sendMessage: (c, username: string, message: string) => {
// save message to persistent storage
c.state.messages.push({ username, message });

// broadcast message to all clients
c.broadcast("newMessage", username, message);
},

getHistory: (c) => {
return c.state.messages;
},
},
});

// Create and export the app
export const app = setup({
actors: { chatRoom },
});

// Export type for client type checking
export type App = typeof app;
23 changes: 23 additions & 0 deletions examples/chat-room-python/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"name": "chat-room-python",
"version": "0.8.0",
"private": true,
"type": "module",
"scripts": {
"dev": "npx @actor-core/cli@latest dev actors/app.ts",
"check-types": "tsc --noEmit",
"pytest": "pytest tests/test_chat_room.py -v"
},
"devDependencies": {
"@actor-core/cli": "workspace:*",
"@types/node": "^22.13.9",
"actor-core": "workspace:*",
"tsx": "^3.12.7",
"typescript": "^5.5.2"
},
"example": {
"platforms": [
"*"
]
}
}
4 changes: 4 additions & 0 deletions examples/chat-room-python/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
actor-core-client>=0.8.0
prompt_toolkit>=3.0.0
pytest>=7.0.0
pytest-asyncio>=0.21.0
52 changes: 52 additions & 0 deletions examples/chat-room-python/scripts/cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import asyncio
from actor_core_client import AsyncClient as ActorClient
import prompt_toolkit
from prompt_toolkit.patch_stdout import patch_stdout
from typing import TypedDict, List

async def init_prompt() -> tuple[str, str]:
username = await prompt_toolkit.prompt_async("Username: ")
room = await prompt_toolkit.prompt_async("Room: ")
return username, room

async def main():
# Get username and room
username, room = await init_prompt()
print(f"Joining room '{room}' as '{username}'")

# Create client and connect to chat room
client = ActorClient("http://localhost:6420")
chat_room = await client.get("chatRoom", tags={"room": room}, params={"room": room})

# Get and display history
history = await chat_room.action("getHistory", [])
if history:
print("\nHistory:")
for msg in history:
print(f"[{msg['username']}] {msg['message']}")

# Set up message handler
def on_message(username: str, message: str):
print(f"\n[{username}] {message}")

chat_room.on_event("newMessage", on_message)

# Main message loop
print("\nStart typing messages (press Ctrl+D or send empty message to exit)")
try:
with patch_stdout():
while True:
# NOTE: Using prompt_toolkit to keep messages
# intact, regardless of other threads / tasks.
message = await prompt_toolkit.prompt_async("\nMessage: ")
if not message:
break
await chat_room.action("sendMessage", [username, message])
except EOFError:
pass
finally:
print("\nDisconnecting...")
await chat_room.disconnect()

if __name__ == "__main__":
asyncio.run(main())
30 changes: 30 additions & 0 deletions examples/chat-room-python/scripts/connect.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import asyncio
import os
from actor_core_client import AsyncClient as ActorClient

async def main():
# Create client
endpoint = os.getenv("ENDPOINT", "http://localhost:6420")
client = ActorClient(endpoint)

# Connect to chat room
chat_room = await client.get("chatRoom")

# Get existing messages
messages = await chat_room.action("getHistory", [])
print("Messages:", messages)

# Listen for new messages
def on_message(username: str, message: str):
print(f"Message from {username}: {message}")

chat_room.on_event("newMessage", on_message)

# Send message to room
await chat_room.action("sendMessage", ["william", "All the world's a stage."])

# Disconnect from actor when finished
await chat_room.disconnect()

if __name__ == "__main__":
asyncio.run(main())
66 changes: 66 additions & 0 deletions examples/chat-room-python/tests/test_chat_room.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import pytest
from actor_core_client import AsyncClient as ActorClient
from actor_core_test import setup_test
from typing import TypedDict, List


async def test_chat_room_should_handle_messages():
# Set up test environment
client = await setup_test()

# Connect to chat room
chat_room = await client.get("chatRoom")

# Initial history should be empty
initial_messages = await chat_room.action("getHistory", [])
assert initial_messages == []

# Test event emission
received_data = {"username": "", "message": ""}

def on_message(username: str, message: str):
received_data["username"] = username
received_data["message"] = message

chat_room.on_event("newMessage", on_message)

# Send a message
test_user = "william"
test_message = "All the world's a stage."
await chat_room.action("sendMessage", [test_user, test_message])

# Verify event was emitted with correct data
assert received_data["username"] == test_user
assert received_data["message"] == test_message

# Verify message was stored in history
updated_messages = await chat_room.action("getHistory", [])
assert updated_messages == [{"username": test_user, "message": test_message}]

# Send multiple messages and verify
users = ["romeo", "juliet", "othello"]
messages = [
"Wherefore art thou?",
"Here I am!",
"The green-eyed monster."
]

for i in range(len(users)):
await chat_room.action("sendMessage", [users[i], messages[i]])

# Verify event emission
assert received_data["username"] == users[i]
assert received_data["message"] == messages[i]

# Verify all messages are in history in correct order
final_history = await chat_room.action("getHistory", [])
expected_history = [{"username": test_user, "message": test_message}]
expected_history.extend([
{"username": users[i], "message": messages[i]}
for i in range(len(users))
])

assert final_history == expected_history

# Cleanup
await chat_room.disconnect()
43 changes: 43 additions & 0 deletions examples/chat-room-python/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
{
"compilerOptions": {
/* Visit https://aka.ms/tsconfig.json to read more about this file */

/* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
"target": "esnext",
/* Specify a set of bundled library declaration files that describe the target runtime environment. */
"lib": ["esnext"],
/* Specify what JSX code is generated. */
"jsx": "react-jsx",

/* Specify what module code is generated. */
"module": "esnext",
/* Specify how TypeScript looks up a file from a given module specifier. */
"moduleResolution": "bundler",
/* Specify type package names to be included without being referenced in a source file. */
"types": ["node"],
/* Enable importing .json files */
"resolveJsonModule": true,

/* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */
"allowJs": true,
/* Enable error reporting in type-checked JavaScript files. */
"checkJs": false,

/* Disable emitting files from a compilation. */
"noEmit": true,

/* Ensure that each file can be safely transpiled without relying on other imports. */
"isolatedModules": true,
/* Allow 'import x from y' when a module doesn't have a default export. */
"allowSyntheticDefaultImports": true,
/* Ensure that casing is correct in imports. */
"forceConsistentCasingInFileNames": true,

/* Enable all strict type-checking options. */
"strict": true,

/* Skip type checking all .d.ts files. */
"skipLibCheck": true
},
"include": ["src/**/*", "actors/**/*", "tests/**/*"]
}
12 changes: 12 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3897,6 +3897,18 @@ __metadata:
languageName: node
linkType: hard

"chat-room-python@workspace:examples/chat-room-python":
version: 0.0.0-use.local
resolution: "chat-room-python@workspace:examples/chat-room-python"
dependencies:
"@actor-core/cli": "workspace:*"
"@types/node": "npm:^22.13.9"
actor-core: "workspace:*"
tsx: "npm:^3.12.7"
typescript: "npm:^5.5.2"
languageName: unknown
linkType: soft

"chat-room@workspace:examples/chat-room":
version: 0.0.0-use.local
resolution: "chat-room@workspace:examples/chat-room"
Expand Down