|
| 1 | +# Step-by-Step Tutorial: Ceylon ProcessPlayGround with ProcessWorker |
| 2 | + |
| 3 | +This tutorial will guide you through creating a simple text processing application using Ceylon's ProcessPlayGround and |
| 4 | +ProcessWorker. We'll build a system that converts text to uppercase. |
| 5 | + |
| 6 | +## Prerequisites |
| 7 | + |
| 8 | +1. Python 3.7 or higher installed |
| 9 | +2. Ceylon package installed |
| 10 | +3. Basic understanding of Python async/await |
| 11 | + |
| 12 | +## Step 1: Project Setup |
| 13 | + |
| 14 | +First, create a new directory for your project and create a new Python file: |
| 15 | + |
| 16 | +```bash |
| 17 | +mkdir ceylon_tutorial |
| 18 | +cd ceylon_tutorial |
| 19 | +touch uppercase_processor.py |
| 20 | +``` |
| 21 | + |
| 22 | +## Step 2: Import Required Dependencies |
| 23 | + |
| 24 | +Open `uppercase_processor.py` and add the necessary imports: |
| 25 | + |
| 26 | +```python |
| 27 | +import asyncio |
| 28 | +from typing import Any |
| 29 | +from ceylon.processor.agent import ProcessWorker |
| 30 | +from ceylon.processor.playground import ProcessPlayGround |
| 31 | +from ceylon.processor.data import ProcessRequest, ProcessResponse, ProcessState |
| 32 | +``` |
| 33 | + |
| 34 | +## Step 3: Create the ProcessWorker |
| 35 | + |
| 36 | +Create a custom ProcessWorker class that will handle the text conversion: |
| 37 | + |
| 38 | +```python |
| 39 | +class UpperCaseProcessor(ProcessWorker): |
| 40 | + """A simple processor that converts input text to uppercase""" |
| 41 | + |
| 42 | + async def _processor(self, request: ProcessRequest, time: int) -> tuple[Any, dict]: |
| 43 | + """Process the input text by converting it to uppercase""" |
| 44 | + try: |
| 45 | + # Convert input to string and make uppercase |
| 46 | + if isinstance(request.data, str): |
| 47 | + result = request.data.upper() |
| 48 | + else: |
| 49 | + result = str(request.data).upper() |
| 50 | + |
| 51 | + # Return result and empty metadata |
| 52 | + return result, {} |
| 53 | + |
| 54 | + except Exception as e: |
| 55 | + raise Exception(f"Error processing text: {str(e)}") |
| 56 | +``` |
| 57 | + |
| 58 | +Key points about the ProcessWorker: |
| 59 | + |
| 60 | +- Inherits from `ProcessWorker` |
| 61 | +- Takes a name parameter in `__init__` |
| 62 | +- Implements `_processor` method that receives a request and returns a tuple of (result, metadata) |
| 63 | + |
| 64 | +## Step 4: Create the Main Function |
| 65 | + |
| 66 | +Add the main function that sets up and runs the playground: |
| 67 | + |
| 68 | +```python |
| 69 | +async def main(): |
| 70 | + # Create playground and worker |
| 71 | + playground = ProcessPlayGround(name="text_playground", port=8888) |
| 72 | + worker = UpperCaseProcessor() |
| 73 | + |
| 74 | + # Start playground with worker |
| 75 | + async with playground.play(workers=[worker]) as pg: |
| 76 | + # Create a process request |
| 77 | + request = ProcessRequest( |
| 78 | + task_type="uppercase_processor", |
| 79 | + data="Hello, World!" |
| 80 | + ) |
| 81 | + |
| 82 | + # Send request and wait for response |
| 83 | + response = await pg.process_request(request) |
| 84 | + |
| 85 | + # Print results |
| 86 | + print(f"Original text: {request.data}") |
| 87 | + print(f"Processed text: {response.result}") |
| 88 | + print(f"Process status: {response.status}") |
| 89 | + |
| 90 | + # Signal playground to finish |
| 91 | + await pg.finish() |
| 92 | +``` |
| 93 | + |
| 94 | +## Step 5: Add Entry Point |
| 95 | + |
| 96 | +Add the entry point at the bottom of the file: |
| 97 | + |
| 98 | +```python |
| 99 | +if __name__ == "__main__": |
| 100 | + # Run the example |
| 101 | + asyncio.run(main()) |
| 102 | +``` |
| 103 | + |
| 104 | +## Understanding the Components |
| 105 | + |
| 106 | +### ProcessPlayGround |
| 107 | + |
| 108 | +- Acts as a central hub for processing requests |
| 109 | +- Manages workers and their connections |
| 110 | +- Handles message routing between components |
| 111 | + |
| 112 | +### ProcessWorker |
| 113 | + |
| 114 | +- Performs the actual processing work |
| 115 | +- Defines processing logic in `_processor` method |
| 116 | +- Can handle specific types of requests based on role |
| 117 | + |
| 118 | +### ProcessRequest |
| 119 | + |
| 120 | +- Contains the data to be processed |
| 121 | +- Includes task type that matches worker role |
| 122 | +- Can include additional metadata |
| 123 | + |
| 124 | +### ProcessResponse |
| 125 | + |
| 126 | +- Contains the processed result |
| 127 | +- Includes status (SUCCESS/ERROR) |
| 128 | +- Can include error messages and metadata |
| 129 | + |
| 130 | +## Running the Example |
| 131 | + |
| 132 | +1. Save all the code in `uppercase_processor.py` |
| 133 | +2. Open a terminal in your project directory |
| 134 | +3. Run the script: |
| 135 | + |
| 136 | +```bash |
| 137 | +python uppercase_processor.py |
| 138 | +``` |
| 139 | + |
| 140 | +Expected output: |
| 141 | + |
| 142 | +``` |
| 143 | +Original text: Hello, World! |
| 144 | +Processed text: HELLO, WORLD! |
| 145 | +Process status: ProcessState.SUCCESS |
| 146 | +``` |
| 147 | + |
| 148 | +## Common Issues and Solutions |
| 149 | + |
| 150 | +1. **Port Already in Use** |
| 151 | + - Error: "Address already in use" |
| 152 | + - Solution: Change the port number in playground initialization |
| 153 | + |
| 154 | +2. **Worker Not Connected** |
| 155 | + - Error: "No worker available for task type" |
| 156 | + - Solution: Ensure worker role matches task_type in request |
| 157 | + |
| 158 | +3. **Async Context Issues** |
| 159 | + - Error: "Event loop is closed" |
| 160 | + - Solution: Ensure all async code is within the main function |
| 161 | + |
| 162 | +## Next Steps |
| 163 | + |
| 164 | +1. Add error handling: |
| 165 | + |
| 166 | +```python |
| 167 | +try: |
| 168 | + response = await pg.process_request(request) |
| 169 | +except Exception as e: |
| 170 | + print(f"Error processing request: {e}") |
| 171 | +``` |
| 172 | + |
| 173 | +2. Process multiple requests: |
| 174 | + |
| 175 | +```python |
| 176 | +requests = [ |
| 177 | + ProcessRequest(task_type="uppercase_processor", data="first request"), |
| 178 | + ProcessRequest(task_type="uppercase_processor", data="second request") |
| 179 | +] |
| 180 | +for request in requests: |
| 181 | + response = await pg.process_request(request) |
| 182 | + print(response.result) |
| 183 | +``` |
| 184 | + |
| 185 | +3. Add metadata to track processing time: |
| 186 | + |
| 187 | +```python |
| 188 | +import time |
| 189 | + |
| 190 | + |
| 191 | +async def _processor(self, request: ProcessRequest, time: int) -> tuple[Any, dict]: |
| 192 | + start_time = time.time() |
| 193 | + result = request.data.upper() |
| 194 | + processing_time = time.time() - start_time |
| 195 | + return result, {"processing_time": processing_time} |
| 196 | +``` |
| 197 | + |
| 198 | +## Tips for Production Use |
| 199 | + |
| 200 | +1. Always implement proper error handling |
| 201 | +2. Use logging instead of print statements |
| 202 | +3. Consider implementing request timeouts |
| 203 | +4. Add monitoring and metrics collection |
| 204 | +5. Implement proper cleanup in case of failures |
| 205 | + |
| 206 | +## Conclusion |
| 207 | + |
| 208 | +This tutorial covered the basics of creating a Ceylon ProcessPlayGround with a custom ProcessWorker. The example |
| 209 | +demonstrates: |
| 210 | + |
| 211 | +- Setting up a processing system |
| 212 | +- Creating custom workers |
| 213 | +- Handling requests and responses |
| 214 | +- Basic error handling |
| 215 | + |
| 216 | +As you build more complex systems, you can extend this pattern to handle different types of data, implement more |
| 217 | +sophisticated processing logic, and add additional features like load balancing and error recovery. |
0 commit comments