# Workflow Basics - Python SDK

> This section explains Workflow Basics with the Python SDK

## Develop a basic Workflow 

**How to develop a basic Workflow using the Temporal Python SDK.**

Workflows are the fundamental unit of a Temporal Application, and it all starts with the development of a
[Workflow Definition](/workflow-definition).

In the Temporal Python SDK programming model, Workflows are defined as classes.

Specify the `@workflow.defn` decorator on the Workflow class to identify a Workflow.

Use the `@workflow.run` to mark the entry point method to be invoked. This must be set on one asynchronous method
defined on the same class as `@workflow.defn`. Run methods have positional parameters.

<div className="copycode-notice-container">
  <a href="https://github.com/temporalio/documentation/blob/main/sample-apps/python/your_app/your_workflows_dacx.py">
    View the source code
  </a>{' '}
  in the context of the rest of the application code.
</div>

```python
from temporalio import workflow
# ...
# ...
@workflow.defn(name="YourWorkflow")
class YourWorkflow:
    @workflow.run
    async def run(self, name: str) -> str:
        return await workflow.execute_activity(
            your_activity,
            YourParams("Hello", name),
            start_to_close_timeout=timedelta(seconds=10),
        )
```

### Define Workflow parameters 

**How to define Workflow parameters using the Temporal Python SDK.**

Temporal Workflows may have any number of custom parameters. However, we strongly recommend that objects are used as
parameters, so that the object's individual fields may be altered without breaking the signature of the Workflow. All
Workflow Definition parameters must be serializable.

Workflow parameters are the method parameters of the singular method decorated with `@workflow.run`. These can be any
data type Temporal can convert, including [`dataclasses`](https://docs.python.org/3/library/dataclasses.html) when
properly type-annotated. Technically this can be multiple parameters, but Temporal strongly encourages a single
`dataclass` parameter containing all input fields.

<div className="copycode-notice-container">
  <a href="https://github.com/temporalio/documentation/blob/main/sample-apps/python/your_app/your_dataobject_dacx.py">
    View the source code
  </a>{' '}
  in the context of the rest of the application code.
</div>

```python
from dataclasses import dataclass
# ...
# ...
@dataclass
class YourParams:
    greeting: str
    name: str
```

### Define Workflow return parameters 

**How to define Workflow return parameters using the Temporal Python SDK.**

Workflow return values must also be serializable. Returning results, returning errors, or throwing exceptions is fairly
idiomatic in each language that is supported. However, Temporal APIs that must be used to get the result of a Workflow
Execution will only ever receive one of either the result or the error.

To return a value of the Workflow, use `return` to return an object.

To return the results of a Workflow Execution, use either `start_workflow()` or `execute_workflow()` asynchronous
methods.

For performance and behavior reasons, users should pass through all modules, including Activities, Nexus services, and
third-party plugins, whose calls will be deterministic using
[`imports_passed_through`](https://python.temporal.io/temporalio.workflow.unsafe.html#imports_passed_through) or at
Worker creation time by customizing the runner's restrictions with
[`with_passthrough_modules`](https://python.temporal.io/temporalio.worker.workflow_sandbox.SandboxRestrictions.html#with_passthrough_modules).

<div className="copycode-notice-container">
  <a href="https://github.com/temporalio/documentation/blob/main/sample-apps/python/your_app/your_workflows_dacx.py">
    View the source code
  </a>{' '}
  in the context of the rest of the application code.
</div>

```python
from temporalio import workflow

with workflow.unsafe.imports_passed_through():
    from your_activities_dacx import your_activity
    from your_dataobject_dacx import YourParams
# ...
@workflow.defn(name="YourWorkflow")
class YourWorkflow:
    @workflow.run
    async def run(self, name: str) -> str:
        return await workflow.execute_activity(
            your_activity,
            YourParams("Hello", name),
            start_to_close_timeout=timedelta(seconds=10),
        )
```

### Customize your Workflow Type 

**How to customize your Workflow Type using the Temporal Python SDK.**

Workflows have a Type that are referred to as the Workflow name.

The following examples demonstrate how to set a custom name for your Workflow Type.

You can customize the Workflow name with a custom name in the decorator argument. For example,
`@workflow.defn(name="your-workflow-name")`. If the name parameter is not specified, the Workflow name defaults to the
unqualified class name.

<div className="copycode-notice-container">
  <a href="https://github.com/temporalio/documentation/blob/main/sample-apps/python/your_app/your_workflows_dacx.py">
    View the source code
  </a>{' '}
  in the context of the rest of the application code.
</div>

```python
from temporalio import workflow

with workflow.unsafe.imports_passed_through():
    from your_activities_dacx import your_activity
    from your_dataobject_dacx import YourParams
# ...
@workflow.defn(name="YourWorkflow")
class YourWorkflow:
    @workflow.run
    async def run(self, name: str) -> str:
        return await workflow.execute_activity(
            your_activity,
            YourParams("Hello", name),
            start_to_close_timeout=timedelta(seconds=10),
        )
```

### Develop Workflow logic 

**How to develop Workflow logic using the Temporal Python SDK.**

Workflow logic is constrained by [deterministic execution requirements](/workflow-definition#deterministic-constraints).
Each Temporal SDK provides a
set of APIs that can be used inside your Workflow to interact with external (to the Workflow) application code.

Workflow code must be deterministic because the Temporal Server may [replay](/develop/python/best-practices/testing-suite#replay) your Workflow to reconstruct its state. This means:

- no threading
- no randomness
- no external calls to processes
- no network I/O
- no global state mutation
- no system date or time

All API safe for Workflows used in the [`temporalio.workflow`](https://python.temporal.io/temporalio.workflow.html) must
run in the implicit [`asyncio` event loop](https://docs.python.org/3/library/asyncio-eventloop.html) and be
_deterministic_.

The SDK provides replay-safe alternatives for common needs:

#### Logging

Use [`workflow.logger`](https://python.temporal.io/temporalio.workflow.html#logger) instead of `print()` or the standard `logging` module.
The SDK logger automatically suppresses log messages during replay to avoid duplicates:

```python
@workflow.defn
class MyWorkflow:
    @workflow.run
    async def run(self, name: str) -> str:
        workflow.logger.info("Starting workflow", name)
        # ...
```

For logger configuration, see [Observability: Log from a Workflow](/develop/python/platform/observability#logging).

#### Random numbers and UUIDs

Use [`workflow.random()`](https://python.temporal.io/temporalio.workflow.html#random) to get a deterministic `random.Random` instance seeded per Workflow Execution. Never use `random.random()` or other `random` module functions directly.
For UUIDs, use [`workflow.uuid4()`](https://python.temporal.io/temporalio.workflow.html#uuid4) instead of `uuid.uuid4()`:

```python
# Good - deterministic across replays
value = workflow.random().randint(1, 100)
unique_id = workflow.uuid4()

# Bad - different result on every replay
import random
value = random.randint(1, 100)
```

#### Current time

Use [`workflow.now()`](https://python.temporal.io/temporalio.workflow.html#now) instead of `datetime.now()` or `time.time()`. The SDK returns the time of the last Workflow Task, which is consistent across replays:

```python
current_time = workflow.now()
```

#### Detecting replay (advanced)

Use [`workflow.unsafe.is_replaying`](https://python.temporal.io/temporalio.workflow.html#is_replaying) to guard code that should only run on the first execution, such as emitting metrics or sending external notifications from an [Interceptor](/develop/python/workers/interceptors).
> **⚠️ Caution:**
>
> Never use this to affect Workflow business logic — branching on replay status breaks determinism.
>

```python
if not workflow.unsafe.is_replaying():
    emit_metric("workflow_started", 1)
```

If your goal is to always take action when something new is happening, check that `workflow.unsafe.is_replaying_history_events()` is false instead. This will be false during read-only operations like queries and update validators.  This is what the SDK's built-in logger and tracing interceptors use internally.
