Skip to content

@activity.defn support and other minor things #16

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Mar 28, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
__pycache__
/build
/dist
/docs/_autosummary
/docs/_build
temporalio/api/*
!temporalio/api/__init__.py
Expand Down
17 changes: 15 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,9 @@ import asyncio
import logging
from temporalio.client import Client
from temporalio.worker import Worker
from temporalio import activity

@activity.defn
async def say_hello_activity(name: str) -> str:
return f"Hello, {name}!"

Expand All @@ -135,7 +137,7 @@ async def main(stop_event: asyncio.Event):
client = await Client.connect("http://localhost:7233", namespace="my-namespace")

# Run the worker until the event is set
worker = Worker(client, task_queue="my-task-queue", activities={"say-hello-activity": say_hello_activity})
worker = Worker(client, task_queue="my-task-queue", activities=[say_hello_activity])
async with worker:
await stop_event.wait()
```
Expand All @@ -148,6 +150,7 @@ Some things to note about the above code:
* Activities are passed as a mapping with the key as a string activity name and the value as a callable
* While this example accepts a stop event and uses `async with`, `run()` and `shutdown()` may be used instead
* Workers can have many more options not shown here (e.g. data converters and interceptors)
* A custom name for the activity can be set with a decorator argument, e.g. `@activity.defn(name="my activity")`

#### Types of Activities

Expand Down Expand Up @@ -276,4 +279,14 @@ The Python SDK is built to work with Python 3.7 and newer. It is built using

```bash
poe test
```
```

### Style

* Mostly [Google Style Guide](https://google.github.io/styleguide/pyguide.html). Notable exceptions:
* We use [Black](https://github.com/psf/black) for formatting, so that takes precedence
* In tests and example code, can import individual classes/functions to make it more readable. Can also do this for
rarely in library code for some Python common items (e.g. `dataclass` or `partial`), but not allowed to do this for
any `temporalio` packages or any classes/functions that aren't clear when unqualified.
* We allow relative imports for private packages
* We allow `@staticmethod`
8 changes: 8 additions & 0 deletions docs/_static/temporal-logo-dark-mode.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 8 additions & 0 deletions docs/_static/temporal-logo-light-mode.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
20 changes: 20 additions & 0 deletions docs/_templates/custom_module_template.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@

{{ fullname | escape | underline}}
===============

.. automodule:: {{ fullname }}
:members:

{% block modules %}
{% if fullname != "temporalio.worker" %}
{% if modules %}
.. autosummary::
:toctree:
:template: custom_module_template.rst
:recursive:
{% for item in modules %}
{{ item }}
{%- endfor %}
{% endif %}
{% endif %}
{% endblock %}
42 changes: 0 additions & 42 deletions docs/api.rst

This file was deleted.

9 changes: 9 additions & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
# ones.
extensions = [
"sphinx.ext.autodoc",
"sphinx.ext.autosummary",
"sphinx.ext.intersphinx",
"sphinx.ext.napoleon",
]
Expand All @@ -54,12 +55,18 @@
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ["_static"]
html_theme_options = {
"light_logo": "temporal-logo-light-mode.svg",
"dark_logo": "temporal-logo-dark-mode.svg",
}

intersphinx_mapping = {
"python": ("https://docs.python.org/3/", None),
"protobuf": ("https://googleapis.dev/python/protobuf/latest/", None),
}

html_title = "API Documentation"

autodoc_docstring_signature = True

autodoc_typehints = "description"
Expand All @@ -75,3 +82,5 @@
autodoc_default_options = {
"special-members": "__aenter__,__aexit__,__init__",
}

autosummary_generate = True
69 changes: 0 additions & 69 deletions docs/direct_api.rst

This file was deleted.

28 changes: 16 additions & 12 deletions docs/index.rst
Original file line number Diff line number Diff line change
@@ -1,22 +1,26 @@
.. Temporal Python SDK documentation master file, created by
sphinx-quickstart on Fri Feb 4 11:52:42 2022.
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.

Temporal Python SDK
===================
Temporal Python SDK API Documentation
=====================================

.. toctree::
:maxdepth: 2
:caption: Contents:
Modules
-------

api
direct_api
.. autosummary::
:toctree: _autosummary
:template: custom_module_template.rst
:recursive:

temporalio.client
temporalio.worker
temporalio.activity
temporalio.converter
temporalio.exceptions
temporalio.api
temporalio.workflow_service


Indices and tables
==================
------------------

* :ref:`genindex`
* :ref:`modindex`
Expand Down
14 changes: 7 additions & 7 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,14 @@ isort = "^5.10.1"
mypy = "^0.931"
mypy-protobuf = "^3.2.0"
pydocstyle = "^6.1.1"
pytest = "^6.2.5"
pytest = "^7.1.1"
pytest-asyncio = "^0.18.0"
pytest-timeout = "^2.1.0"
setuptools = "^60.9.3"
setuptools-rust = "^1.1.2"
Sphinx = "^4.4.0"
sphinxcontrib-napoleon = "^0.7"
toml = "^0.10.2"
twine = "^3.8.0"

[tool.poe.tasks]
Expand Down
46 changes: 46 additions & 0 deletions temporalio/activity.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import threading
from dataclasses import dataclass
from datetime import datetime, timedelta
from functools import partial
from typing import (
Any,
Callable,
Expand All @@ -24,12 +25,57 @@
NoReturn,
Optional,
Tuple,
TypeVar,
overload,
)

import temporalio.api.common.v1
import temporalio.common
import temporalio.exceptions

ActivityFunc = TypeVar("ActivityFunc", bound=Callable[..., Any])


@overload
def defn(fn: ActivityFunc) -> ActivityFunc:
...


@overload
def defn(*, name: str) -> Callable[[ActivityFunc], ActivityFunc]:
...


def defn(fn: Optional[ActivityFunc] = None, *, name: Optional[str] = None):
"""Decorator for activity functions.

Activities can be async or non-async.

Args:
fn: The function to decorate.
name: Name to use for the activity. Defaults to function ``__name__``.
"""

def with_name(name: str, fn: ActivityFunc) -> ActivityFunc:
# Validate the activity
if not callable(fn):
raise TypeError("Activity is not callable")
elif not fn.__code__:
raise TypeError("Activity callable missing __code__")
elif fn.__code__.co_kwonlyargcount:
raise TypeError("Activity cannot have keyword-only arguments")
# Set the name
setattr(fn, "__temporal_activity_name", name)
return fn

# If name option is available, return decorator function
if name is not None:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should return partial if fn is None, otherwise defn() doesn't work.

Copy link
Member Author

@cretz cretz Mar 28, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I did that then defn sans parens wouldn't work. There is no overload that supports defn() from the type perspective, so if they are calling it, they are violating the docs/spec here.

I could hack it to have name have some kind of tertiary value to support defn() but I would rather not support that form of decorator at all. I suppose I can have a runtime check that if both fn and name are None that it's an error.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WDYM no overload that supports defn()? Both params are optional.
I see no harm in supporting defn(), what are you concerned about?

Copy link
Member Author

@cretz cretz Mar 28, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both params are optional in the non-overloaded function. Calling the non-@overload will fail a type checker with:

All overload variants of "defn" require at least one argument

I see no harm in supporting defn(), what are you concerned about?

There is no harm, there's just no benefit. I will add a runtime failure too.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Runtime failure added

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am merging this to get started on the rest of the workflow impl, but we can revisit this if needed.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh I missed the overloads there.

Not critical but really don't see a good reason not to support that..

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But I don't see a good reason to support it.

return partial(with_name, name)
if fn is None:
raise RuntimeError("Cannot invoke defn without function or name")
# Otherwise just run decorator function
return with_name(fn.__name__, fn)


@dataclass(frozen=True)
class Info:
Expand Down
1 change: 1 addition & 0 deletions temporalio/api/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""gRPC API."""
2 changes: 1 addition & 1 deletion temporalio/converter.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Base converter and default implementations for conversion to/from values/payloads."""
"""Base converter and implementations for data conversion."""

import dataclasses
import inspect
Expand Down
Loading