-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add asynchronous client for pyfirecrest (#65)
* ✨ Add typing * make upload/download more permissive * apply black * Add types in to documentation * Update types * fix formatting * Add undoc-members in types reference * First implementation of AsyncFirecrest client * Add httpx in the dependencies * Add some basic example for the async client * Set polling rate in example * Fix import * Fix external transfer * Add example for external transfers * Add async external objects in reference * Expose async external objects * Fix issue with mixed responses in case of errors * Get the access token before stalling requests * Add documentation for async client + refactor * Decouple async and sync clients * Split ExternalStorage objects to another file * Fix typo * Fix typo in filename * Refactor status tests * Add auth handler * Refactor compute tests * Refactor extra tests * Refactor reservation tests * Refactor storage tests * Refactor utilities tests * Duplicate compute testing for async version * Add compute tests * Add extra tests for async client * Add reservation async tests + small fixes * Add status tests for async client + small fixes * Add utilities tests + fix async whoami * Add storage tests for async version * Remove unused imports and fix annotation * Small fixes * Fix type errors * Small fix in docs * Remove old installation instructions --------- Co-authored-by: Chris Sewell <[email protected]>
- Loading branch information
1 parent
cb541bc
commit 72bb870
Showing
31 changed files
with
5,094 additions
and
1,054 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
# Examples for asyncio with pyfirecrest | ||
|
||
### Simple asynchronous workflow with the new client | ||
|
||
Here is an example of how to use the `AsyncFirecrest` client with asyncio. | ||
|
||
```python | ||
import firecrest | ||
import asyncio | ||
import logging | ||
|
||
|
||
# Setup variables before running the script | ||
client_id = "" | ||
client_secret = "" | ||
token_uri = "" | ||
firecrest_url = "" | ||
|
||
machine = "" | ||
local_script_path = "" | ||
|
||
# Ignore this part, it is simply setup for logging | ||
logger = logging.getLogger("simple_example") | ||
logger.setLevel(logging.DEBUG) | ||
ch = logging.StreamHandler() | ||
ch.setLevel(logging.DEBUG) | ||
formatter = logging.Formatter("%(asctime)s - %(message)s", datefmt="%H:%M:%S") | ||
ch.setFormatter(formatter) | ||
logger.addHandler(ch) | ||
|
||
async def workflow(client, i): | ||
logger.info(f"{i}: Starting workflow") | ||
job = await client.submit(machine, local_script_path) | ||
logger.info(f"{i}: Submitted job with jobid: {job['jobid']}") | ||
while True: | ||
poll_res = await client.poll_active(machine, [job["jobid"]]) | ||
if len(poll_res) < 1: | ||
logger.info(f"{i}: Job {job['jobid']} is no longer active") | ||
break | ||
|
||
logger.info(f"{i}: Job {job['jobid']} status: {poll_res[0]['state']}") | ||
await asyncio.sleep(30) | ||
|
||
output = await client.view(machine, job["job_file_out"]) | ||
logger.info(f"{i}: job output: {output}") | ||
|
||
|
||
async def main(): | ||
auth = firecrest.ClientCredentialsAuth(client_id, client_secret, token_uri) | ||
client = firecrest.AsyncFirecrest(firecrest_url, authorization=auth) | ||
|
||
# Set up the desired polling rate for each microservice. The float number | ||
# represents the number of seconds between consecutive requests in each | ||
# microservice. Default is 5 seconds for now. | ||
client.time_between_calls = { | ||
"compute": 5, | ||
"reservations": 5, | ||
"status": 5, | ||
"storage": 5, | ||
"tasks": 5, | ||
"utilities": 5, | ||
} | ||
|
||
workflows = [workflow(client, i) for i in range(5)] | ||
await asyncio.gather(*workflows) | ||
|
||
|
||
asyncio.run(main()) | ||
|
||
``` | ||
|
||
|
||
### External transfers with `AsyncFirecrest` | ||
|
||
The uploads and downloads work as before but you have to keep in mind which methods are coroutines. | ||
|
||
```python | ||
# Download | ||
down_obj = await client.external_download("cluster", "/remote/path/to/the/file") | ||
status = await down_obj.status | ||
print(status) | ||
await down_obj.finish_download("my_local_file") | ||
|
||
# Upload | ||
up_obj = await client.external_upload("cluster", "/path/to/local/file", "/remote/path/to/filesystem") | ||
await up_obj.finish_upload() | ||
status = await up_obj.status | ||
print(status) | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
Asynchronous FirecREST objects | ||
============================== | ||
|
||
The library also provides an asynchronous API for the client: | ||
|
||
The ``AsyncFirecrest`` class | ||
**************************** | ||
.. autoclass:: firecrest.AsyncFirecrest | ||
:members: | ||
:undoc-members: | ||
:show-inheritance: | ||
|
||
|
||
The ``AsyncExternalDownload`` class | ||
*********************************** | ||
.. autoclass:: firecrest.AsyncExternalDownload | ||
:inherited-members: | ||
:members: | ||
:undoc-members: | ||
:show-inheritance: | ||
|
||
|
||
The ``AsyncExternalUpload`` class | ||
********************************* | ||
.. autoclass:: firecrest.AsyncExternalUpload | ||
:inherited-members: | ||
:members: | ||
:undoc-members: | ||
:show-inheritance: |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
Reference | ||
========= | ||
|
||
.. toctree:: | ||
:maxdepth: 2 | ||
:caption: Contents: | ||
|
||
reference_basic | ||
reference_async | ||
reference_cli |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
How to use the asynchronous API [experimental] | ||
============================================== | ||
|
||
In this tutorial, we will explore the asynchronous API of the pyFirecREST library. | ||
Asynchronous programming is a powerful technique that allows you to write more efficient and responsive code by handling concurrent tasks without blocking the main execution flow. | ||
This capability is particularly valuable when dealing with time-consuming operations such as network requests, I/O operations, or interactions with external services. | ||
|
||
In order to take advantage of the asynchronous client you may need to make many changes in your existing code, so the effort is worth it when you develop a code from the start or if you need to make a large number of requests. | ||
You could submit hundreds or thousands of jobs, set a reasonable rate and pyFirecREST will handle it in the background without going over the request rate limit or overflowing the system. | ||
|
||
If you are already familiar with the synchronous version of pyFirecREST, you will find it quite straightforward to adapt to the asynchronous paradigm. | ||
|
||
We will be going through an example that will use the `asyncio library <https://docs.python.org/3/library/asyncio.html>`__. | ||
First you will need to create an ``AsyncFirecrest`` object, instead of the simple ``Firecrest`` object. | ||
|
||
.. code-block:: Python | ||
client = fc.AsyncFirecrest( | ||
firecrest_url=<firecrest_url>, | ||
authorization=MyAuthorizationClass() | ||
) | ||
As you can see in the reference, the methods of ``AsyncFirecrest`` have the same name as the ones from the simple client, with the same arguments and types, but you will need to use the async/await syntax when you call them. | ||
|
||
Here is an example of the calls we saw in the previous section: | ||
|
||
.. code-block:: Python | ||
# Getting all the systems | ||
systems = await client.all_systems() | ||
print(systems) | ||
# Getting the files of a directory | ||
files = await client.list_files("cluster", "/home/test_user") | ||
print(files) | ||
# Submit a job | ||
job = await client.submit("cluster", "script.sh") | ||
print(job) | ||
The uploads and downloads work as before but you have to keep in mind which methods are coroutines. | ||
|
||
.. code-block:: Python | ||
# Download | ||
down_obj = await client.external_download("cluster", "/remote/path/to/the/file") | ||
status = await down_obj.status | ||
print(status) | ||
await down_obj.finish_download("my_local_file") | ||
# Upload | ||
up_obj = await client.external_upload("cluster", "/path/to/local/file", "/remote/path/to/filesystem") | ||
await up_obj.finish_upload() | ||
status = await up_obj.status | ||
print(status) | ||
Here is a more complete example for how you could use the asynchronous client: | ||
|
||
|
||
.. code-block:: Python | ||
import firecrest | ||
import asyncio | ||
import logging | ||
# Setup variables before running the script | ||
client_id = "" | ||
client_secret = "" | ||
token_uri = "" | ||
firecrest_url = "" | ||
machine = "" | ||
local_script_path = "" | ||
# This is simply setup for logging, you can ignore it | ||
logger = logging.getLogger("simple_example") | ||
logger.setLevel(logging.DEBUG) | ||
ch = logging.StreamHandler() | ||
ch.setLevel(logging.DEBUG) | ||
formatter = logging.Formatter("%(asctime)s - %(message)s", datefmt="%H:%M:%S") | ||
ch.setFormatter(formatter) | ||
logger.addHandler(ch) | ||
async def workflow(client, i): | ||
logger.info(f"{i}: Starting workflow") | ||
job = await client.submit(machine, local_script_path) | ||
logger.info(f"{i}: Submitted job with jobid: {job['jobid']}") | ||
while True: | ||
poll_res = await client.poll_active(machine, [job["jobid"]]) | ||
if len(poll_res) < 1: | ||
logger.info(f"{i}: Job {job['jobid']} is no longer active") | ||
break | ||
logger.info(f"{i}: Job {job['jobid']} status: {poll_res[0]['state']}") | ||
await asyncio.sleep(30) | ||
output = await client.view(machine, job["job_file_out"]) | ||
logger.info(f"{i}: job output: {output}") | ||
async def main(): | ||
auth = firecrest.ClientCredentialsAuth(client_id, client_secret, token_uri) | ||
client = firecrest.AsyncFirecrest(firecrest_url, authorization=auth) | ||
# Set up the desired polling rate for each microservice. The float number | ||
# represents the number of seconds between consecutive requests in each | ||
# microservice. | ||
client.time_between_calls = { | ||
"compute": 5, | ||
"reservations": 5, | ||
"status": 5, | ||
"storage": 5, | ||
"tasks": 5, | ||
"utilities": 5, | ||
} | ||
workflows = [workflow(client, i) for i in range(5)] | ||
await asyncio.gather(*workflows) | ||
asyncio.run(main()) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.