Skip to content

Commit c796ace

Browse files
ctx.random() and ctx.uuid() for deterministic random and uuid. (#120)
1 parent 2a9dcd7 commit c796ace

File tree

4 files changed

+67
-0
lines changed

4 files changed

+67
-0
lines changed

examples/example.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,14 @@
1515
import restate
1616

1717
from greeter import greeter
18+
from random_greeter import random_greeter
1819
from virtual_object import counter
1920
from workflow import payment
2021
from pydantic_greeter import pydantic_greeter
2122
from concurrent_greeter import concurrent_greeter
2223

2324
app = restate.app(services=[greeter,
25+
random_greeter,
2426
counter,
2527
payment,
2628
pydantic_greeter,

examples/random_greeter.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
#
2+
# Copyright (c) 2023-2024 - Restate Software, Inc., Restate GmbH
3+
#
4+
# This file is part of the Restate SDK for Python,
5+
# which is released under the MIT license.
6+
#
7+
# You can find a copy of the license in file LICENSE in the root
8+
# directory of this repository or package, or at
9+
# https://github.com/restatedev/sdk-typescript/blob/main/LICENSE
10+
#
11+
"""example.py"""
12+
# pylint: disable=C0116
13+
# pylint: disable=W0613
14+
15+
from restate import Service, Context
16+
17+
random_greeter = Service("random_greeter")
18+
19+
@random_greeter.handler()
20+
async def greet(ctx: Context, name: str) -> str:
21+
22+
# ctx.random() returns a Python Random instance seeded deterministically.
23+
# By using ctx.random() you don't write entries in the journal,
24+
# but you still get the same generated values on retries.
25+
26+
# To generate random numbers
27+
random_number = ctx.random().randint(0, 100)
28+
29+
# To generate random bytes
30+
random_bytes = ctx.random().randbytes(10)
31+
32+
# Use ctx.uuid() to generate a UUID v4 seeded deterministically
33+
# As with ctx.random(), this won't write entries in the journal
34+
random_uuid = ctx.uuid()
35+
36+
return (f"Hello {name} with "
37+
f"random number {random_number}, "
38+
f"random bytes {random_bytes!r} "
39+
f"and uuid {random_uuid}!")

python/restate/context.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
"""
1515

1616
import abc
17+
from random import Random
18+
from uuid import UUID
1719
from dataclasses import dataclass
1820
from typing import Any, Awaitable, Callable, Dict, List, Optional, TypeVar, Union, Coroutine, overload, ParamSpec
1921
import typing
@@ -219,6 +221,21 @@ def request(self) -> Request:
219221
Returns the request object.
220222
"""
221223

224+
@abc.abstractmethod
225+
def random(self) -> Random:
226+
"""
227+
Returns a Random instance inherently predictable, deterministically seeded by Restate.
228+
229+
This instance is useful to generate identifiers, idempotency keys, and for uniform sampling from a set of options.
230+
"""
231+
232+
@abc.abstractmethod
233+
def uuid(self) -> UUID:
234+
"""
235+
Returns a random UUID, deterministically seeded.
236+
237+
This UUID will be stable across retries and replays.
238+
"""
222239

223240
@typing_extensions.deprecated("`run` is deprecated, use `run_typed` instead for better type safety")
224241
@overload

python/restate/server_context.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,14 @@
1818

1919
import asyncio
2020
import contextvars
21+
from random import Random
2122
from datetime import timedelta
2223
import inspect
2324
import functools
2425
from typing import Any, Awaitable, Callable, Dict, List, Optional, TypeVar, Union, Coroutine
2526
import typing
2627
import traceback
28+
from uuid import UUID
2729

2830
from restate.context import DurablePromise, AttemptFinishedEvent, HandlerType, ObjectContext, Request, RestateDurableCallFuture, RestateDurableFuture, RunAction, SendHandle, RestateDurableSleepFuture, RunOptions, P
2931
from restate.exceptions import TerminalError
@@ -278,6 +280,7 @@ def __init__(self,
278280
self.invocation = invocation
279281
self.attempt_headers = attempt_headers
280282
self.send = send
283+
self.random_instance = Random(invocation.random_seed)
281284
self.receive = receive
282285
self.run_coros_to_execute: dict[int, Callable[[], Awaitable[None]]] = {}
283286
self.request_finished_event = asyncio.Event()
@@ -471,6 +474,12 @@ def request(self) -> Request:
471474
attempt_finished_event=ServerTeardownEvent(self.request_finished_event),
472475
)
473476

477+
def random(self) -> Random:
478+
return self.random_instance
479+
480+
def uuid(self) -> UUID:
481+
return UUID(int=self.random_instance.getrandbits(128), version=4)
482+
474483
# pylint: disable=R0914
475484
async def create_run_coroutine(self,
476485
handle: int,

0 commit comments

Comments
 (0)