Skip to content

Commit

Permalink
Merge pull request #2 from dymmond/feat/docs
Browse files Browse the repository at this point in the history
Docs and tests
  • Loading branch information
tarsil authored Jan 4, 2024
2 parents 0d18b4f + 6f04ef6 commit 9ca9691
Show file tree
Hide file tree
Showing 11 changed files with 482 additions and 21 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,4 @@ jobs:
TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }}
- name: "Deploy docs"
run: |
curl -X POST '${{ secrets.DEPLOY_DOCS }}'
curl -X POST '${{ secrets.DEPLOY_DOCS }}'
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ background tasks, which means you can even use it with [Esmerald][esmerald], [Fa
without using the native version of each framework but **also allows** to run this inside anything else
like **Django** for example.

**Due to the nature of the package, this is only available from Python 3.10+.**

## How to use it

This package is actually very simple to use it, really, there is no rocket science since the
Expand Down
9 changes: 8 additions & 1 deletion backgrounder/decorator.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import asyncio
import functools
from typing import Any, Callable

import anyio.from_thread
import anyio.to_thread
import nest_asyncio
import sniffio

from backgrounder.concurrency import AsyncCallable
from backgrounder.tasks import Task

nest_asyncio.apply()


def background(fn: Callable[..., Any]) -> Callable[..., Any]:
"""
Expand All @@ -24,7 +29,9 @@ def wrapper(*args: Any, **kwargs: Any) -> Any:
task = Task(fn, *args, **kwargs)
try:
sniffio.current_async_library()
return anyio.from_thread.run(fn, *args, **kwargs)
async_callable = AsyncCallable(fn)
loop = asyncio.get_event_loop()
return loop.run_until_complete(async_callable(*args, **kwargs))
except sniffio.AsyncLibraryNotFoundError:
return anyio.run(task)

Expand Down
306 changes: 300 additions & 6 deletions backgrounder/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from typing import Any, Callable, Sequence, Union

import anyio
from typing_extensions import Annotated, Doc

from backgrounder._internal import Repr
from backgrounder.concurrency import enforce_async_callable
Expand All @@ -17,12 +18,115 @@

class Task(Repr):
"""
The representation of a background task.
`Task` as a single instance can be easily achieved.
**Example**
```python
from backgrounder import Task
async def send_email_notification(message: str):
'''
Sends an email notification
'''
send_notification(message)
task = Task(send_email_notification, "message to someone")
```
"""

__slots__ = ("func", "args", "kwargs")

def __init__(self, func: Callable[P, Any], *args: P.args, **kwargs: P.kwargs) -> None:
def __init__(
self,
func: Annotated[
Callable[P, Any],
Doc(
"""
Any callable to be executed by in the background.
This can be `async def` of normal blocking `def`.
**Example**
```python
from backgrounder import Task
# For blocking callables
def send_notification(message: str) -> None:
...
task = Task(send_notification, "A notification")
await task()
# For async callables
async def send_notification(message: str) -> None:
...
task = Task(send_notification, "A notification")
await task()
```
"""
),
],
*args: Annotated[
Any,
Doc(
"""
Any arguments of the callable.
**Example**
```python
from backgrounder import Task
# For blocking callables
def send_notification(message: str, email: str) -> None:
...
task = Task(send_notification, "A notification", "[email protected]")
# For async callables
async def send_notification(message: str, email: str) -> None:
...
task = Task(send_notification, "A notification", "[email protected]")
```
"""
),
],
**kwargs: Annotated[
Any,
Doc(
"""
Any kwyword arguments of the callable.
**Example**
```python
from typing import Any
from backgrounder import Task
data = {"message": "A notification", "email": "[email protected]"}
# For blocking callables
def send_notification(**kwargs: Any) -> None:
message = kwargs.pop("message", None)
email = kwargs.pop("email", None)
task = Task(send_notification, **data)
# For async callables
async def send_notification(**kwargs: Any) -> None:
message = kwargs.pop("message", None)
email = kwargs.pop("email", None)
task = Task(send_notification, **data)
```
"""
),
],
) -> None:
self.func = enforce_async_callable(func)
self.args = args
self.kwargs = kwargs
Expand All @@ -33,19 +137,209 @@ async def __call__(self) -> None:

class Tasks(Task):
"""
A container for background tasks.
Alternatively, the `Tasks` can also be used to be passed
in.
**Example**
```python
from datetime import datetime
from backgrounder import Task, Tasks
async def send_email_notification(message: str):
'''
Sends an email notification
'''
send_notification(message)
def write_in_file():
with open("log.txt", mode="w") as log:
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
content = f"Notification sent @ {now}"
log.write(content)
tasks = Tasks([
Task(send_email_notification, message="Account created"),
Task(write_in_file),
])
await tasks()
```
When `as_group` is set to True, it will run all the tasks concurrently (as a group)
**Example**
```python
from datetime import datetime
from backgrounder import Task, Tasks
async def send_email_notification(message: str):
'''
Sends an email notification
'''
send_notification(message)
When `as_group` is set to True, it will run all the tasks
concurrently (as a group)
def write_in_file():
with open("log.txt", mode="w") as log:
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
content = f"Notification sent @ {now}"
log.write(content)
tasks = Tasks([
Task(send_email_notification, message="Account created"),
Task(write_in_file),
], as_group=True)
await tasks()
```
"""

__slots__ = ("tasks", "as_group")

def __init__(self, tasks: Union[Sequence[Task], None] = None, as_group: bool = False):
def __init__(
self,
tasks: Annotated[
Union[Sequence[Task], None],
Doc(
"""
A `list` of [tasks](#tasks) to run execute.
**Example**
```python
from datetime import datetime
from backgrounder import Task, Tasks
async def send_email_notification(message: str):
'''
Sends an email notification
'''
send_notification(message)
def write_in_file():
with open("log.txt", mode="w") as log:
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
content = f"Notification sent @ {now}"
log.write(content)
tasks = Tasks([
Task(send_email_notification, message="Account created"),
Task(write_in_file),
])
await tasks()
```
"""
),
] = None,
as_group: Annotated[
bool,
Doc(
"""
Boolean flag indicating if the tasks should be run concurrently, in other
words, as a group.
**Example**
```python
from datetime import datetime
from backgrounder import Task, Tasks
async def send_email_notification(message: str):
'''
Sends an email notification
'''
send_notification(message)
def write_in_file():
with open("log.txt", mode="w") as log:
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
content = f"Notification sent @ {now}"
log.write(content)
tasks = Tasks([
Task(send_email_notification, message="Account created"),
Task(write_in_file),
], as_group=True)
await tasks()
```
"""
),
] = False,
):
self.tasks = list(tasks) if tasks else []
self.as_group = as_group

def add_task(self, func: Callable[P, Any], *args: P.args, **kwargs: P.kwargs) -> None:
"""
Another way of adding tasks to the `Tasks` object.
**Example**
```python
from datetime import datetime
from backgrounder import Task, Tasks
async def send_email_notification(message: str):
'''
Sends an email notification
'''
send_notification(message)
def write_in_file():
with open("log.txt", mode="w") as log:
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
content = f"Notification sent @ {now}"
log.write(content)
tasks = Tasks()
tasks.add_task(send_email_notification, message="Account created")
tasks.add_task(write_in_file)
await tasks()
```
Or if you want to run them concurrently.
**Example**
```python
from datetime import datetime
from backgrounder import Task, Tasks
async def send_email_notification(message: str):
'''
Sends an email notification
'''
send_notification(message)
def write_in_file():
with open("log.txt", mode="w") as log:
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
content = f"Notification sent @ {now}"
log.write(content)
tasks = Tasks(as_group=True)
tasks.add_task(send_email_notification, message="Account created")
tasks.add_task(write_in_file)
await tasks()
```
"""
task = Task(func, *args, **kwargs)
self.tasks.append(task)

Expand Down
2 changes: 2 additions & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ background tasks, which means you can even use it with [Esmerald][esmerald], [Fa
without using the native version of each framework but **also allows** to run this inside anything else
like **Django** for example.

**Due to the nature of the package, this is only available from Python 3.10+.**

## How to use it

This package is actually very simple to use it, really, there is no rocket science since the
Expand Down
Loading

0 comments on commit 9ca9691

Please sign in to comment.