Skip to content

Commit 677f8be

Browse files
authored
Move from Temporalite to Temporal CLI dev server (#339)
Fixes #272
1 parent aff198b commit 677f8be

File tree

7 files changed

+99
-83
lines changed

7 files changed

+99
-83
lines changed

temporalio/bridge/src/lib.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ fn temporal_sdk_bridge(py: Python, m: &PyModule) -> PyResult<()> {
2020

2121
// Testing stuff
2222
m.add_class::<testing::EphemeralServerRef>()?;
23-
m.add_function(wrap_pyfunction!(start_temporalite, m)?)?;
23+
m.add_function(wrap_pyfunction!(start_dev_server, m)?)?;
2424
m.add_function(wrap_pyfunction!(start_test_server, m)?)?;
2525

2626
// Worker stuff
@@ -55,12 +55,12 @@ fn raise_in_thread<'a>(py: Python<'a>, thread_id: std::os::raw::c_long, exc: &Py
5555
}
5656

5757
#[pyfunction]
58-
fn start_temporalite<'a>(
58+
fn start_dev_server<'a>(
5959
py: Python<'a>,
6060
runtime_ref: &runtime::RuntimeRef,
61-
config: testing::TemporaliteConfig,
61+
config: testing::DevServerConfig,
6262
) -> PyResult<&'a PyAny> {
63-
testing::start_temporalite(py, &runtime_ref, config)
63+
testing::start_dev_server(py, &runtime_ref, config)
6464
}
6565

6666
#[pyfunction]

temporalio/bridge/src/testing.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ pub struct EphemeralServerRef {
1111
}
1212

1313
#[derive(FromPyObject)]
14-
pub struct TemporaliteConfig {
14+
pub struct DevServerConfig {
1515
existing_path: Option<String>,
1616
sdk_name: String,
1717
sdk_version: String,
@@ -38,17 +38,17 @@ pub struct TestServerConfig {
3838
extra_args: Vec<String>,
3939
}
4040

41-
pub fn start_temporalite<'a>(
41+
pub fn start_dev_server<'a>(
4242
py: Python<'a>,
4343
runtime_ref: &runtime::RuntimeRef,
44-
config: TemporaliteConfig,
44+
config: DevServerConfig,
4545
) -> PyResult<&'a PyAny> {
46-
let opts: ephemeral_server::TemporaliteConfig = config.try_into()?;
46+
let opts: ephemeral_server::TemporalDevServerConfig = config.try_into()?;
4747
let runtime = runtime_ref.runtime.clone();
4848
runtime_ref.runtime.future_into_py(py, async move {
4949
Ok(EphemeralServerRef {
5050
server: Some(opts.start_server().await.map_err(|err| {
51-
PyRuntimeError::new_err(format!("Failed starting Temporalite: {}", err))
51+
PyRuntimeError::new_err(format!("Failed starting Temporal dev server: {}", err))
5252
})?),
5353
runtime,
5454
})
@@ -105,11 +105,11 @@ impl EphemeralServerRef {
105105
}
106106
}
107107

108-
impl TryFrom<TemporaliteConfig> for ephemeral_server::TemporaliteConfig {
108+
impl TryFrom<DevServerConfig> for ephemeral_server::TemporalDevServerConfig {
109109
type Error = PyErr;
110110

111-
fn try_from(conf: TemporaliteConfig) -> PyResult<Self> {
112-
ephemeral_server::TemporaliteConfigBuilder::default()
111+
fn try_from(conf: DevServerConfig) -> PyResult<Self> {
112+
ephemeral_server::TemporalDevServerConfigBuilder::default()
113113
.exe(if let Some(existing_path) = conf.existing_path {
114114
ephemeral_server::EphemeralExe::ExistingPath(existing_path.to_owned())
115115
} else {

temporalio/bridge/testing.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@
1313

1414

1515
@dataclass
16-
class TemporaliteConfig:
17-
"""Python representation of the Rust struct for configuring Temporalite."""
16+
class DevServerConfig:
17+
"""Python representation of the Rust struct for configuring dev server."""
1818

1919
existing_path: Optional[str]
2020
sdk_name: str
@@ -48,12 +48,12 @@ class EphemeralServer:
4848
"""Python representation of a Rust ephemeral server."""
4949

5050
@staticmethod
51-
async def start_temporalite(
52-
runtime: temporalio.bridge.runtime.Runtime, config: TemporaliteConfig
51+
async def start_dev_server(
52+
runtime: temporalio.bridge.runtime.Runtime, config: DevServerConfig
5353
) -> EphemeralServer:
54-
"""Start a Temporalite instance."""
54+
"""Start a dev server instance."""
5555
return EphemeralServer(
56-
await temporalio.bridge.temporal_sdk_bridge.start_temporalite(
56+
await temporalio.bridge.temporal_sdk_bridge.start_dev_server(
5757
runtime._ref, config
5858
)
5959
)

temporalio/client.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2411,6 +2411,14 @@ class ScheduleRange:
24112411
Unset or 0 defaults as 1.
24122412
"""
24132413

2414+
def __post_init__(self):
2415+
"""Set field defaults."""
2416+
# Class is frozen, so we must setattr bypassing dataclass setattr
2417+
if self.end < self.start:
2418+
object.__setattr__(self, "end", self.start)
2419+
if self.step == 0:
2420+
object.__setattr__(self, "step", 1)
2421+
24142422
@staticmethod
24152423
def _from_protos(
24162424
ranges: Sequence[temporalio.api.schedule.v1.Range],

temporalio/testing/_workflow.py

Lines changed: 43 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -91,12 +91,12 @@ async def start_local(
9191
download_dest_dir: Optional[str] = None,
9292
ui: bool = False,
9393
runtime: Optional[temporalio.runtime.Runtime] = None,
94-
temporalite_existing_path: Optional[str] = None,
95-
temporalite_database_filename: Optional[str] = None,
96-
temporalite_log_format: str = "pretty",
97-
temporalite_log_level: Optional[str] = "warn",
98-
temporalite_download_version: str = "default",
99-
temporalite_extra_args: Sequence[str] = [],
94+
dev_server_existing_path: Optional[str] = None,
95+
dev_server_database_filename: Optional[str] = None,
96+
dev_server_log_format: str = "pretty",
97+
dev_server_log_level: Optional[str] = "warn",
98+
dev_server_download_version: str = "default",
99+
dev_server_extra_args: Sequence[str] = [],
100100
) -> WorkflowEnvironment:
101101
"""Start a full Temporal server locally, downloading if necessary.
102102
@@ -106,16 +106,15 @@ async def start_local(
106106
environment. :py:meth:`sleep` will sleep the actual amount of time and
107107
:py:meth:`get_current_time` will return the current time.
108108
109-
Internally, this uses
110-
`Temporalite <https://github.com/temporalio/temporalite>`_. Which is a
111-
self-contained binary for Temporal using Sqlite persistence. This will
112-
download Temporalite to a temporary directory by default if it has not
113-
already been downloaded before and ``temporalite_existing_path`` is not
114-
set.
109+
Internally, this uses the Temporal CLI dev server from
110+
https://github.com/temporalio/cli. This is a self-contained binary for
111+
Temporal using Sqlite persistence. This call will download the CLI to a
112+
temporary directory by default if it has not already been downloaded
113+
before and ``dev_server_existing_path`` is not set.
115114
116-
In the future, the Temporalite implementation may be changed to another
117-
implementation. Therefore, all ``temporalite_`` prefixed parameters are
118-
Temporalite specific and may not apply to newer versions.
115+
In the future, the dev server implementation may be changed to another
116+
implementation. Therefore, all ``dev_server_`` prefixed parameters are
117+
dev-server specific and may not apply to newer versions.
119118
120119
Args:
121120
namespace: Namespace name to use for this environment.
@@ -137,55 +136,55 @@ async def start_local(
137136
port: Port number to bind to, or an OS-provided port by default.
138137
download_dest_dir: Directory to download binary to if a download is
139138
needed. If unset, this is the system's temporary directory.
140-
ui: If ``True``, will start a UI in Temporalite.
139+
ui: If ``True``, will start a UI in the dev server.
141140
runtime: Specific runtime to use or default if unset.
142-
temporalite_existing_path: Existing path to the Temporalite binary.
141+
dev_server_existing_path: Existing path to the CLI binary.
143142
If present, no download will be attempted to fetch the binary.
144-
temporalite_database_filename: Path to the Sqlite database to use
145-
for Temporalite. Unset default means only in-memory Sqlite will
146-
be used.
147-
temporalite_log_format: Log format for Temporalite.
148-
temporalite_log_level: Log level to use for Temporalite. Default is
149-
``warn``, but if set to ``None`` this will translate the Python
150-
logger's level to a Temporalite level.
151-
temporalite_download_version: Specific Temporalite version to
152-
download. Defaults to ``default`` which downloads the version
153-
known to work best with this SDK.
154-
temporalite_extra_args: Extra arguments for the Temporalite binary.
143+
dev_server_database_filename: Path to the Sqlite database to use
144+
for the dev server. Unset default means only in-memory Sqlite
145+
will be used.
146+
dev_server_log_format: Log format for the dev server.
147+
dev_server_log_level: Log level to use for the dev server. Default
148+
is ``warn``, but if set to ``None`` this will translate the
149+
Python logger's level to a dev server log level.
150+
dev_server_download_version: Specific CLI version to download.
151+
Defaults to ``default`` which downloads the version known to
152+
work best with this SDK.
153+
dev_server_extra_args: Extra arguments for the CLI binary.
155154
156155
Returns:
157-
The started Temporalite workflow environment.
156+
The started CLI dev server workflow environment.
158157
"""
159158
# Use the logger's configured level if none given
160-
if not temporalite_log_level:
159+
if not dev_server_log_level:
161160
if logger.isEnabledFor(logging.DEBUG):
162-
temporalite_log_level = "debug"
161+
dev_server_log_level = "debug"
163162
elif logger.isEnabledFor(logging.INFO):
164-
temporalite_log_level = "info"
163+
dev_server_log_level = "info"
165164
elif logger.isEnabledFor(logging.WARNING):
166-
temporalite_log_level = "warn"
165+
dev_server_log_level = "warn"
167166
elif logger.isEnabledFor(logging.ERROR):
168-
temporalite_log_level = "error"
167+
dev_server_log_level = "error"
169168
else:
170-
temporalite_log_level = "fatal"
171-
# Start Temporalite
169+
dev_server_log_level = "fatal"
170+
# Start CLI dev server
172171
runtime = runtime or temporalio.runtime.Runtime.default()
173-
server = await temporalio.bridge.testing.EphemeralServer.start_temporalite(
172+
server = await temporalio.bridge.testing.EphemeralServer.start_dev_server(
174173
runtime._core_runtime,
175-
temporalio.bridge.testing.TemporaliteConfig(
176-
existing_path=temporalite_existing_path,
174+
temporalio.bridge.testing.DevServerConfig(
175+
existing_path=dev_server_existing_path,
177176
sdk_name="sdk-python",
178177
sdk_version=temporalio.service.__version__,
179-
download_version=temporalite_download_version,
178+
download_version=dev_server_download_version,
180179
download_dest_dir=download_dest_dir,
181180
namespace=namespace,
182181
ip=ip,
183182
port=port,
184-
database_filename=temporalite_database_filename,
183+
database_filename=dev_server_database_filename,
185184
ui=ui,
186-
log_format=temporalite_log_format,
187-
log_level=temporalite_log_level,
188-
extra_args=temporalite_extra_args,
185+
log_format=dev_server_log_format,
186+
log_level=dev_server_log_level,
187+
extra_args=dev_server_extra_args,
189188
),
190189
)
191190
# If we can't connect to the server, we should shut it down

tests/conftest.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ def env_type(request: pytest.FixtureRequest) -> str:
7676
async def env(env_type: str) -> AsyncGenerator[WorkflowEnvironment, None]:
7777
if env_type == "local":
7878
env = await WorkflowEnvironment.start_local(
79-
temporalite_extra_args=[
79+
dev_server_extra_args=[
8080
"--dynamic-config-value",
8181
"system.forceSearchAttributesCacheRefreshOnRead=true",
8282
]

tests/worker/test_workflow.py

Lines changed: 29 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
Optional,
2121
Sequence,
2222
Tuple,
23-
Type,
2423
cast,
2524
)
2625

@@ -32,11 +31,11 @@
3231
from temporalio.api.common.v1 import Payload, Payloads, WorkflowExecution
3332
from temporalio.api.enums.v1 import EventType, IndexedValueType
3433
from temporalio.api.failure.v1 import Failure
35-
from temporalio.api.operatorservice.v1 import AddSearchAttributesRequest
36-
from temporalio.api.workflowservice.v1 import (
37-
GetSearchAttributesRequest,
38-
GetWorkflowExecutionHistoryRequest,
34+
from temporalio.api.operatorservice.v1 import (
35+
AddSearchAttributesRequest,
36+
ListSearchAttributesRequest,
3937
)
38+
from temporalio.api.workflowservice.v1 import GetWorkflowExecutionHistoryRequest
4039
from temporalio.bridge.proto.workflow_activation import WorkflowActivation
4140
from temporalio.bridge.proto.workflow_completion import WorkflowActivationCompletion
4241
from temporalio.client import (
@@ -1356,9 +1355,11 @@ def do_search_attribute_update(self) -> None:
13561355
empty_float_list: List[float] = []
13571356
workflow.upsert_search_attributes(
13581357
{
1359-
f"{sa_prefix}text": ["text3"],
1360-
# We intentionally leave keyword off to confirm it still comes back
1361-
f"{sa_prefix}int": [123, 456],
1358+
f"{sa_prefix}text": ["text2"],
1359+
# We intentionally leave keyword off to confirm it still comes
1360+
# back but replace keyword list
1361+
f"{sa_prefix}keyword_list": ["keywordlist3", "keywordlist4"],
1362+
f"{sa_prefix}int": [456],
13621363
# Empty list to confirm removed
13631364
f"{sa_prefix}double": empty_float_list,
13641365
f"{sa_prefix}bool": [False],
@@ -1374,18 +1375,20 @@ async def test_workflow_search_attributes(client: Client, env_type: str):
13741375
pytest.skip("Only testing search attributes on local which disables cache")
13751376

13761377
async def search_attributes_present() -> bool:
1377-
resp = await client.workflow_service.get_search_attributes(
1378-
GetSearchAttributesRequest()
1378+
resp = await client.operator_service.list_search_attributes(
1379+
ListSearchAttributesRequest(namespace=client.namespace)
13791380
)
1380-
return any(k for k in resp.keys.keys() if k.startswith(sa_prefix))
1381+
return any(k for k in resp.custom_attributes.keys() if k.startswith(sa_prefix))
13811382

13821383
# Add search attributes if not already present
13831384
if not await search_attributes_present():
13841385
await client.operator_service.add_search_attributes(
13851386
AddSearchAttributesRequest(
1387+
namespace=client.namespace,
13861388
search_attributes={
13871389
f"{sa_prefix}text": IndexedValueType.INDEXED_VALUE_TYPE_TEXT,
13881390
f"{sa_prefix}keyword": IndexedValueType.INDEXED_VALUE_TYPE_KEYWORD,
1391+
f"{sa_prefix}keyword_list": IndexedValueType.INDEXED_VALUE_TYPE_KEYWORD_LIST,
13891392
f"{sa_prefix}int": IndexedValueType.INDEXED_VALUE_TYPE_INT,
13901393
f"{sa_prefix}double": IndexedValueType.INDEXED_VALUE_TYPE_DOUBLE,
13911394
f"{sa_prefix}bool": IndexedValueType.INDEXED_VALUE_TYPE_BOOL,
@@ -1402,29 +1405,31 @@ async def search_attributes_present() -> bool:
14021405
id=f"workflow-{uuid.uuid4()}",
14031406
task_queue=worker.task_queue,
14041407
search_attributes={
1405-
f"{sa_prefix}text": ["text1", "text2", "text0"],
1408+
f"{sa_prefix}text": ["text1"],
14061409
f"{sa_prefix}keyword": ["keyword1"],
1410+
f"{sa_prefix}keyword_list": ["keywordlist1", "keywordlist2"],
14071411
f"{sa_prefix}int": [123],
14081412
f"{sa_prefix}double": [456.78],
14091413
f"{sa_prefix}bool": [True],
14101414
f"{sa_prefix}datetime": [
1411-
# With UTC
1412-
datetime(2001, 2, 3, 4, 5, 6, tzinfo=timezone.utc),
1413-
# With other offset
1414-
datetime(2002, 3, 4, 5, 6, 7, tzinfo=timezone(timedelta(hours=8))),
1415+
datetime(2001, 2, 3, 4, 5, 6, tzinfo=timezone.utc)
14151416
],
14161417
},
14171418
)
14181419
# Make sure it started with the right attributes
14191420
expected = {
1420-
f"{sa_prefix}text": {"type": "str", "values": ["text1", "text2", "text0"]},
1421+
f"{sa_prefix}text": {"type": "str", "values": ["text1"]},
14211422
f"{sa_prefix}keyword": {"type": "str", "values": ["keyword1"]},
1423+
f"{sa_prefix}keyword_list": {
1424+
"type": "str",
1425+
"values": ["keywordlist1", "keywordlist2"],
1426+
},
14221427
f"{sa_prefix}int": {"type": "int", "values": [123]},
14231428
f"{sa_prefix}double": {"type": "float", "values": [456.78]},
14241429
f"{sa_prefix}bool": {"type": "bool", "values": [True]},
14251430
f"{sa_prefix}datetime": {
14261431
"type": "datetime",
1427-
"values": ["2001-02-03 04:05:06+00:00", "2002-03-04 05:06:07+08:00"],
1432+
"values": ["2001-02-03 04:05:06+00:00"],
14281433
},
14291434
}
14301435
assert expected == await handle.query(
@@ -1434,9 +1439,13 @@ async def search_attributes_present() -> bool:
14341439
# Do an attribute update and check query
14351440
await handle.signal(SearchAttributeWorkflow.do_search_attribute_update)
14361441
expected = {
1437-
f"{sa_prefix}text": {"type": "str", "values": ["text3"]},
1442+
f"{sa_prefix}text": {"type": "str", "values": ["text2"]},
14381443
f"{sa_prefix}keyword": {"type": "str", "values": ["keyword1"]},
1439-
f"{sa_prefix}int": {"type": "int", "values": [123, 456]},
1444+
f"{sa_prefix}keyword_list": {
1445+
"type": "str",
1446+
"values": ["keywordlist3", "keywordlist4"],
1447+
},
1448+
f"{sa_prefix}int": {"type": "int", "values": [456]},
14401449
f"{sa_prefix}double": {"type": "<unknown>", "values": []},
14411450
f"{sa_prefix}bool": {"type": "bool", "values": [False]},
14421451
f"{sa_prefix}datetime": {

0 commit comments

Comments
 (0)