Releases: temporalio/sdk-python
1.11.0
Highlights
Priority (Pre-release)
Users can now set a priority key when scheduling a workflow, activity or child workflows. The priority key will be used to help prioritize certain tasks over others when there is a backlog. Priority is currently not supported in any OSS Temporal release, but support will be coming soon. To experiment with this feature please see the pre-release development server or if you are a Temporal Cloud customer reach out to your SA.
Versioning / Safe Deploy (Pre-release)
This release introduces a preview of new APIs that gracefully manage code changes and worker pools that support them. The goal is to better control which workers should execute new, and existing, workflows and activities tasks, based on their code and configuration.
AUTO_UPGRADE
and PINNED
are two Versioning Behaviors that can be specified on a workflow implementation using the versioning_behavior
argument to @workflow.defn
. PINNED
workflows are typically short lived, and are never affected by new versions, i.e., they do not need to use the patch API for compatibility. AUTO_UPGRADE
workflows are mostly long running, but they need to use patching to safely transition to new versions. The choice of PINNED
vs AUTO_UPGRADE
ultimately depends on your willingness to keep old worker fleets running vs the complexity of patching.
To manage Worker Deployments please use the Temporal CLI, or Client.workflow_service
Automatic Poller Scaling
You can configure Workers to automatically adjust the number of concurrent polls they make for tasks! Set the workflow_task_poller_behavior
and activity_task_poller_behavior
arguments of the Worker
constructor to PollerBehaviorAutoscaling
to try it out. You can expect fewer unnecessary polls during low load, and increased polls during high load when they can be used to increase task throughput.
Specific Changes
2025-02-19 - 49ca10e - uv + maturin migration (#768)
2025-03-26 - b0dfaef - Evolve observability documentation (#797)
2025-03-27 - 65b10bf - Silence passing test (#804)
2025-03-27 - d219870 - Check for grpc status details before indexing & unpacking (#801)
2025-03-28 - bf747f1 - Workflow/Activity Priorities (#802)
2025-03-31 - 92b7758 - Expose root workflow execution (#805)
2025-04-04 - 7ffa822 - Improve workflow task deadlock and eviction (#806)
2025-04-07 - dfc6e7f - Add workflow.in_workflow() utility (#799)
2025-04-10 - 1296cd7 - Allow even-parentless workflow spans to always be created (#817)
2025-04-10 - f5e6d20 - Unskip update tests under java test server (#644)
2025-04-10 - fb3dccd - Treat TimeoutError as workflow/update failure instead of task failure (#800)
2025-04-16 - fd49c4e - Worker Deployment Versioning (#821)
2025-04-18 - 127835e - Expose poller autoscaling options (#830)
2025-04-18 - 4933dc5 - Data converter non-string keys (#833)
2025-04-18 - 52bc5cf - Investigate test flakes (#832)
2025-04-21 - 3d10ba6 - Enhancement/allow custom metric buckets (#781)
2025-04-21 - 6f31556 - Fix protobuf version in build-binaries wheel test (#836)
2025-04-22 - 2025f07 - Add dynamic config function (#842)
2025-04-23 - bc82930 - Add VERCEL_PROJECT_ID and VERCEL_ORG_ID (#844)
1.10.0
Get from PyPI
Highlights
Python 3.13 is officially supported
Python 3.13 is now officially supported and Python 3.8 is no longer supported.
Pydantic is officially supported
The SDK now contains a data converter that supports conversion of all types supported by Pydantic to and from JSON. In addition to Pydantic models, these include all json.dump
-able types, various non-json.dump
-able standard library types such as dataclasses, types from the datetime module, sets, UUID, etc, and custom types composed of any of these. Usage:
from temporalio.client import Client
from temporalio.contrib.pydantic import pydantic_data_converter
client = Client(data_converter=pydantic_data_converter, ...)
This data converter supports Pydantic v2 only.
See https://github.com/temporalio/sdk-python?tab=readme-ov-file#data-conversion for details
Specific Changes
2025-01-10 - 7af48a7 - Drop Python 3.8, add 3.13 (#694)
2025-01-15 - 7665bf5 - Useful event ordering tests from discarded changes to event loop (#729)
2025-01-16 - 1a68b58 - More precise logging (#734)
2025-01-16 - bd44efa - Respond to server changes to Update error (#735)
2025-01-17 - 9da5e69 - Use non-test name for workflow classes (#736)
2025-01-17 - 9e34301 - Add tests of asyncio.Lock and asyncio.Semaphore usage (#567)
2025-01-18 - effc857 - Run-time check for update validator signature (#723)
2025-01-21 - 154aab9 - Fix issue when failing to convert failure (#727)
2025-01-21 - fe46e1a - Remove "experimental" notices from update APIs (#707)
2025-01-22 - 1d89d75 - Fix some lint errors (#744)
2025-01-23 - 150878f - Do not include self
parameter (#746)
2025-01-23 - 45aa3a2 - Expose http option for OTLP (#741)
2025-01-23 - 4892714 - Support passing through all modules (#737)
2025-01-24 - 044b1de - Add workflow.instance() API for obtaining current workflow instance (#739)
2025-01-24 - 07d3567 - chore: remove Python < 3.9 support (#728) (#730)
2025-01-25 - 35a0e6c - Update core (#745)
2025-01-27 - 51f4b66 - Update schedule search attributes (#753)
2025-02-07 - acde42c - Accept search attributes for dev server (#562)
2025-02-13 - 77a1502 - Remove experimental warnings from stable APIs (#766)
2025-02-13 - b3f3662 - Pydantic data converter (#757)
2025-02-14 - 8333900 - Conditionally whitelist datetime.datetime and add tests (#767)
1.9.0
Get from PyPI
Highlights
Update-With-Start (experimental)
Update-with-start sends an update request and starts a workflow if necessary. A WorkflowIDConflictPolicy
must be specified. If the workflow execution is not running, then a new workflow execution is started and the update is sent in the first workflow task. Alternatively, if the specified workflow execution is running then, if the WorkflowIDConflictPolicy
is USE_EXISTING
, the update is issued against the specified workflow, and if the WorkflowIDConflictPolicy
is FAIL
, an error is returned.
See the lazy_initialization sample
User Metadata (experimental)
When starting workflows (directly, via update with start, via schedules, or via child workflows), users can now set a static_summary
and/or static_details
option which may appear in the UI/CLI in the future. Similarly, users can provide summary
to timers and activity invocations. Finally, users can invoke workflow.set_current_details
with a string that can updated be throughout the life of the workflow based on the workflow's state. This value may also appear in the UI/CLI in the future. Values for summary or details can be in limited single-line or multi-line markdown format, respectively. This feature is currently experimental which means future releases can technically update the API in incompatible ways.
Custom Slot Suppliers in Worker Tuners (experimental)
Worker tuners can now be created with custom slot suppliers. By providing a class implementing CustomSlotSupplier
to a worker tuner, users can now control logic of when slots are available for use by the worker. This allows advanced, dynamic control over in-process tuning for how many concurrent activities, local activities, and workflow tasks can run. This is an advanced feature and is currently experimental which means future releases can technically update the API in incompatible ways.
Specific Changes
2024-10-30 - 0b327b0 - Upgrade tonic to v0.12.3 to fix security vulnerability (#680)
2024-11-06 - 5b897b1 - Update Core / InitializeWorkflow (#683)
2024-11-06 - 723d234 - Include update info in logging output (#664)
2024-11-13 - 001ce8b - pyproject.toml: Removed protoc-wheel dependency (#684)
2024-11-20 - 042e088 - Custom slot suppliers (#690)
2024-11-22 - 853889c - Fix logic bug in create_schedule() re. backfills (#693)
2024-11-22 - 97a2b7a - Worker code cleanup (#692)
2024-12-03 - a90f6d4 - Slot info should be optional on release context (#695)
2024-12-06 - 173826f - Add limit to list workflows (#698)
2024-12-16 - 341d949 - User metadata (#701)
2024-12-19 - 540faeb - Update-with-start (#702)
2024-12-19 - 999c8f8 - Set run id in update handle (#705)
2024-12-19 - c44a6d8 - Update bug fix: prevent update from a stale workflow handle (#703)
1.8.0
Get from PyPI
Highlights
- New
@workflow.init
decorator allows__init__
to receive the workflow arguments. This can be useful for message handlers, since these may execute before the@workflow.run
method.
Specific Changes
2024-09-24 - 0995ae0 - Workflow init (#645)
2024-09-25 - 3cf1b85 - Change client-side default to match server-side default (#649)
2024-09-25 - a1b5d65 - cargo update -p quinn-proto (#651)
2024-09-30 - 2e6d742 - Insert guard clause for workflow.upsert_search_attributes on empty inputs (#630)
2024-10-03 - badbb9e - Add documentation of signal and update handlers (#658)
1.7.1
Get from PyPI
Highlights
This is mostly a bugfix release meant to address:
- Unable to send OpenTelemetry metrics over TLS
- Sporadic activity completion RPC cancellation
Specific Changes
2024-08-26 - 4e97841 - Create and upload junit-xml artifacts (#617)
2024-08-26 - 927abdc - Start requiring pyright typechecking (#619)
2024-08-27 - 4aef4bf - Stack trace on deadlock detection exception (#626)
2024-08-27 - 7af99d0 - Fix exception-swallowing code path (#623)
2024-09-06 - 59d04a6 - Make client.py typecheck under pyright (#628)
2024-09-06 - a18140f - Pin to 3.12.4 (#635)
2024-09-11 - 09ac120 - Update core and update release version to 1.7.1 (#640)
1.7.0
Get from PyPI
Highlights
Workflow Update changes
💥 Breaking change on start_update
wait_for_stage=WorkflowUpdateStage.ACCEPTED
is now required when starting an update with temporalio.client.WorkflowHandle.start_update
. Although no other wait_for_stage
value is currently valid, making it required emphasizes that an update caller must wait for a response from the worker, whether using start_update
or execute_update
(in contrast to temporalio.client.Client.start_workflow
and temporalio.client.WorkflowHandle.signal
).
Waiting for handlers to finish
Workflow code can use await workflow.wait_condition(workflow.all_handlers_finished)
to wait for all update and signal handlers to complete. The worker will emit a warning every time a workflow completes or continues-as-new while signal/update handlers are still running. In general workflow code should wait for handlers to finish; an update caller will see an exception if the workflow does not wait for the update to complete.
Use the SDK's versions of asyncio.as_completed
and asyncio.wait
asyncio.as_completed
and asyncio.wait
are sometimes non-deterministic so must not be used in workflow code. Instead use the drop-in replacements provided by the SDK: workflow.as_completed
and workflow.wait
. In a future release it will be an error to use non-deterministic versions from asyncio
. When applying this change to your workflow code it should be considered backwards-incompatible, so use versioning when making this change for running workflows.
current_update_info()
Use temporalio.workflow.current_update_info()
, similar to temporalio.workflow.info()
, to obtain metadata about the current update. This includes the update ID, which is useful for in-workflow deduplication of updates (necessary when using updates with continue-as-new).
Resource-based Worker Auto-tuning (EXPERIMENTAL)
Experimental support for worker tuning has been added along with an implementation for auto-tuning based on available
resources. The temporalio.worker.Worker
class now has a temporalio.worker.Tuner
field that can be set with an instance of
temporalio.worker.WorkerTuner
. This can be a fixed-size based tuner via WorkerTuner.create_fixed
or based
on resources via WorkerTuner.create_resource_based
. Technically the interface can be manually implemented to return
custom SlotSupplier
s, but only fixed-size and resource-based slot suppliers are currently supported, custom slot
suppliers will appear in the future.
This SDK API is experimental and may change in incompatible ways in the future.
Specific Changes
2024-05-09 - f96679b - Fix issue with codecs returning passed-in payloads (#526)
2024-05-15 - a52f25d - During eviction, set is_replaying and raise special exception (#524)
2024-05-20 - 11a97d1 - Required wait update stage, update polling improvements, and other update changes (#521)
2024-05-28 - afadc15 - Allow proper stack trace on eviction deadlock (#530)
2024-06-04 - 2061835 - Update core/dependencies and call worker.validate (#541)
2024-06-04 - 365cead - Add deterministic alternatives for asyncio.wait and asyncio.as_completed (#533)
2024-06-06 - 2d65d82 - Fix GHA config for Swatinem/rust-cache@v2 (#546)
2024-06-06 - 58d6951 - Access current update info with ID inside update handler (#544)
2024-06-10 - 927415a - Remove proto wheel and other minor fixes (#547)
2024-06-17 - 18b890e - Server 1.24 related fixes (#551)
2024-06-17 - 2bb211e - Install protoc in run-bench CI job (#550)
2024-06-18 - 4f646c2 - Add __enhanced_stack_trace query to workers (#537)
2024-06-20 - 2331aa4 - Add WorkflowUpdateRPCTimeoutOrCancelledError (#548)
2024-06-26 - 2c1ac54 - New API to wait for handler executions to complete and warnings on unfinished handler executions (#556)
2024-06-26 - 7ac4445 - Expose resource based auto-tuner options (#559)
2024-07-01 - 38d9eef - Use ruff to auto-format code and sort imports. (#566)
2024-07-09 - 530cadf - skip files that are already mapped for enhanced stack traces (#574)
2024-07-10 - bcbacc2 - Experimental cloud operations client (#570)
2024-07-12 - c57df81 - Support for workflow ID conflict policy (#579)
2024-07-17 - 913b4b6 - Support query for listing schedules (#581)
2024-07-17 - e409d32 - Use minimal scope with pytest.raises (#582)
2024-07-18 - 3796ec3 - Use GH ARM runner (#580)
2024-07-19 - c4b1a01 - Update cibuildwheel (#589)
2024-07-24 - a839196 - Updated lazy loaded logger_details (#593)
2024-08-05 - 50914c4 - Honor all non-completion commands (#569)
2024-08-06 - 9142cdd - Add omes image build (#602)
2024-08-08 - 4b93d1a - Switch omes build to post merge (#604)
2024-08-09 - a5b9661 - Create commands after payload conversion (#591)
2024-08-19 - 73a1673 - Support activity retry delay (#571)
2024-08-19 - 97688cc - Unfinished handlers: add rule to warning message; test CAN, dynamic, and late-registered handlers (#612)
2024-08-20 - 185ce8c - Update Core (#605)
1.6.0
Get from PyPI
Highlights
Safe Eviction
The internal eviction logic has been overhauled to make sure that asyncio tasks are completed before a workflow is removed from the cache and garbage collected. For all properly running workflows this should not cause any issues. However, workflows that may have been developed in invalid ways in the past could have a problem being evicted from cache and will log as such. Users should keep an eye out for logs when upgrading to this version.
Experimental Workflow Failure Type Customization
When a workflow raises an exception that wasn't ApplicationError
or bubbled from a Temporal call (e.g. activity failure), it would be a "task failure" which puts the workflow in a suspended state retrying the task until a code fix is deployed. An experimental feature is now available that lets users set which exception types should instead fail the workflow instead of suspending it. Users can now set the per-workflow failure_exception_types
on the @workflow.defn
decorator or set the worker-level workflow_failure_exception_types
when creating the Worker
. If an exception extends from one of these when raised, it will fail the workflow when raised.
HTTP CONNECT Proxy Support
Python clients now officially support HTTP CONNECT proxies. The http_connect_proxy_config
argument can now be set when connecting a Client
.
Worker Client Replacement
Workers can now have the client they use replaced without shutting down the worker. The client
property on the Worker
can be set to a different connected client. This is useful for users that need to change options like mTLS client certificate without worker downtime. The worker will not interrupt any polling calls but will start using the new client for subsequent calls.
API Key Client Option
If you use API keys in your Temporal server (i.e. Bearer
tokens on Authorization
headers), you can now set api_key
when connecting a Client
or set the api_key
property on an existing client to update it.
Floats and Durations in Metrics
Users using metric_meter()
directly to create metrics (off of runtime, activity, or workflow) can now create float-based histograms and gauges, and duration-based histograms. Similarly users using MetricBuffer
to retrieve metrics can say how they want to retrieve durations (float seconds, int milliseconds, or timedelta
). A durations_as_seconds
option was added to telemetry options for users that prefer seconds as float-based durations to OpenTelemetry and Prometheus instead of the default of integer milliseconds.
Counting Workflows
A new count_workflows
call has been added to the Client
that takes the same query as list_workflows
but is optimized just to perform counts.
💥 Activity/Workflow extra
on LogRecord
Changed
Activity logs and workflow logs used to set activity_info
and workflow_info
on LogRecord
respectively as the full info dataclasses by default. This was not usable by many third-party logging tools that can only work with dict
s and not dataclass
es. Therefore the default was changed to set temporal_activity
and temporal_workflow
on the respective log records to dict
s of commonly needed information. temporalio.activity.logger.full_activity_info_on_extra
and/or temporalio.workflow.logger.full_workflow_info_on_extra
can be set to True
to put those info entries back on extra
.
This only affects those using advanced log handlers that leverage contextual state. The log messages themselves are unaffected.
Specific Changes
2024-03-04 - 477aa31 - Fix execute_child_workflow apidoc (#483)
2024-03-13 - f3d1b85 - API key client option (#486)
2024-03-26 - 36fe961 - Remove experimental flag from start_delay (#492)
2024-04-01 - 13d18ca - Update core and add durations-as-seconds metric option (#498)
2024-04-02 - b07e75e - Change default "extra" contents of activity/workflow logs (#490)
2024-04-05 - 1001653 - HTTP CONNECT proxy support (#501)
2024-04-05 - 466da16 - Safe Eviction (#499)
2024-04-12 - b45447e - Add macOS ARM runner (#506)
2024-04-12 - cf4c7cb - Ensure extra data on task fail logs (#502)
2024-04-18 - 50c2033 - Support float and duration metrics (#508)
2024-04-19 - ecd703d - Add Client.count_workflows (#510)
2024-04-30 - 0bb94f8 - Ability for certain task failure types to fail workflow (#516)
2024-05-03 - 0687151 - Worker client replacement (#517)
1.5.1
Get from PyPI
Highlights
This is mostly a bug-fix and small-issue release. There are no large notable highlights.
Specific Changes
2024-01-10 - e3630ef - Fix line number in workflow.logger (#460)
2024-01-11 - 0bff022 - Add wheel
to dev-dependencies (#457)
2024-01-12 - 10d099b - Fix typing of methods for pyright (#461)
2024-01-12 - 4904054 - Add build id to workflow info (#458)
2024-01-29 - 02a974c - Modularize gen-protos script (#465)
2024-02-01 - 5fe85be - Prefix some errors with rule identifiers (#464)
2024-02-02 - 50768df - Apply eviction before completing activation (#466)
2024-02-13 - 8dce5b5 - Fix SingleParam types for pyright checking (#471)
2024-02-14 - b0a7b4e - Flag MD5 as non-security related usage for FIPS compatibility (#472)
2024-02-20 - 1cfdaa8 - Add workflow ID on not-found activity error message (#428)
2024-02-22 - f31f927 - Ensure update input and responses are run through converters (#478)
2024-02-27 - 73e832d - Update core, minor README/doc updates, and put delete-workflow on proper service (#480)
1.5.0
Get from PyPI
Highlights
Drop Python 3.7 support
Python 3.7 is EOL and is not supported by this library. In addition, we also added CI to confirm 3.12 works properly.
Fix upserting new search attributes
Last release, a bug was introduced that caused an error when upserting search attributes that weren't set on start. This has been fixed.
Specific Changes
2023-11-16 - 06cfd03 - Drop Python 3.7 support, ensure 3.12 support, update dependencies, and implement asyncio.timeout (#422)
2023-11-17 - 75e528b - Delete .github/workflows/semgrep.yml (#431)
2023-11-27 - c47e3f1 - Enable Eager Workflow Start (#430)
2023-11-30 - 6f966c7 - Fix bugs in upsert search attribs (#440)
1.4.0
Get from PyPI
Highlights
Typed Search Attributes
Previously, search attributes were untyped dictionaries with string keys and list values. This causes ambiguity on what types a search attribute key represents and leads to errors. Also, the way search attributes were created or upserted allowed mistakes to occur and was not clear to the server exactly what was expected.
Among other classes, temporalio.common.TypedSearchAttributes
and temporalio.common.SearchAttributeKey
have been added. Search attributes are now best used by creating a global SearchAttributeKey
(e.g. SearchAttributeKey.for_int("my-key")
) and then referencing that key when getting or mutating search attributes. The TypedSearchAttributes
collection (and workflow.typed_search_attributes()
) should now be used when creating/accessing search attributes or a warning will emitted. Users should use the keys when accessing the collection or call value_set
or value_unset
on them to build update objects to pass to temporalio.workflow.upsert_search_attributes
when upserting.
Advanced Metrics Support
Previously, metrics were generated and exposed from our Core layer in a way that made them neither reusable nor exportable in a flexible way. We have now both exposed the ability to record metrics via Core and to received buffered metrics from Core.
To record metrics via Core, we have exposed a temporalio.common.MetricMeter
via temporalio.activity.metric_meter()
, temporalio.workflow.metric_meter()
, and temporalio.runtime.Runtime.metric_meter
. This meter provides a limited interface to generate metrics. It is not meant to abstract everything one can do with a metric system and users are still encouraged to use their own metrics libraries as needed. The workflow metric meter is built to skip recording meters during replay.
To configure metrics in the SDK, a new runtime must be created with telemetry config. To capture metrics emitted by Core, temporalio.runtime.TelemetryConfig.metrics
now accepts a temporalio.runtime.MetricBuffer
instance. That same instance must then be used to call retrieve_updates
on repeatedly and frequently to get metrics coming out of Core. These come as raw events that can then filtered and/or sent to any other metrics system of choice.
Log Forwarding from Core (experimental)
To configure Core logging in the SDK, a new runtime must be created with telemetry config. Previously, Core logs would be sent to the console which wasn't always desirable. Now, the temporalio.runtime.LoggingConfig.forwarding
field can be set with a temporalio.runtime.LogForwardingConfig
class which accepts a logging.Logger
to send core logs to. This is currently experimental while we gather feedback.
Several Minor Additions
temporalio.workflow.Info.get_current_history_size()
added to get history size (get_current_history_length()
was already present)temporalio.workflow.Info.is_continue_as_new_suggested()
added to know whether server has suggested to continue as new- Client keep alive enabled by default (30s interval, 15s timeout), configurable via
keep_alive_config
when connecting - Added
start_delay
totemporalio.client.Client.start_workflow
andexecute_workflow
to support delaying a workflow start - Added experimental support for workflow update which is only available in bleeding edge open source server environments
💥 BREAKING CHANGES
ScheduleActionStartWorkflow.search_attributes
replaced with ScheduleActionStartWorkflow.typed_search_attributes
The rarely used ScheduleActionStartWorkflow.search_attributes
has been replaced by ScheduleActionStartWorkflow.typed_search_attributes
since that object is used in both directions and we can't determine user intent. Users using the old field name in either direction will see an exception immediately on client side (i.e. no behavior changes or accidental misuse). We also added an untyped_search_attributes
field to this class to let untyped ones stay present on update.
Removed temporalio.runtime.TelemetryConfig.tracing
temporalio.runtime.TelemetryConfig.tracing
and its associated class have been removed. This was not general purpose tracing, this was advanced tracing for debugging internal Core logc only and should not have been used by any users. All user-facing tracing remains untouched.
Specific Changes
2023-08-01 - 393e5c8 - Provide Span.Kind for TracingInterceptor (#356)
2023-08-02 - 24fea4c - Expose history size and continue-as-new-suggested (#361)
2023-08-02 - 63f63ae - Upgrade grcpio to 1.53.0 (#359)
2023-08-07 - 59fbccf - Fix interceptors in testing environment (#364)
2023-08-16 - 31358d1 - Add ignore_unknown_fields as an argument to JSONProtoPayloadConverter (#365)
2023-08-24 - 40daaaa - Wait for activity completions on worker shutdown (#370)
2023-08-31 - aa829d3 - Update core/tonic, update metric options, remove core tracing (#380)
2023-09-25 - 3fade95 - Custom metric support (#384)
2023-09-28 - d5edb71 - Fix logging of error on activation failure (#389)
2023-10-05 - d03f356 - Buffered metrics (#391)
2023-10-06 - ded3747 - Encourage threaded activities, warn when max_workers too low, and other small changes (#387)
2023-10-09 - 00878ad - Client keep alive support (#396)
2023-10-24 - 4242dfb - README updates and CI fixes (#403)
2023-10-24 - 97814c2 - Workflow start delay (#406)
2023-10-25 - 2c15ed3 - Workflow Update (#400)
2023-10-30 - fd938c4 - Update last cleanup items (#410)
2023-11-02 - 063b9bf - Update core to get bugfix (#411)
2023-11-02 - 7c0a464 - Typed search attributes (#366)
2023-11-06 - 4802d2f - Log forwarding support (#413)
2023-11-06 - 6dbe2f4 - Fix docstring (#414)
2023-11-07 - d6646a2 - Fix pyd file name for Windows (#417)