Skip to content
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

Logging #134

Merged
merged 64 commits into from
Apr 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
cda0f87
feat: moving to runnable
vijayvammi Feb 24, 2024
f4ffd24
Merge pull request #1 from vijayvammi/runnable
vijayvammi Feb 24, 2024
1b4e241
feat: Bringing in docs
vijayvammi Feb 24, 2024
5d6bd7c
Merge pull request #2 from vijayvammi/docs
vijayvammi Feb 24, 2024
f685613
feat: Bring in more renaming
vijayvammi Feb 25, 2024
b601867
feat: SDK execute will work
vijayvammi Mar 1, 2024
15d615f
feat: simplified data passing
vijayvammi Mar 4, 2024
35c2917
feat: making local executor only serial
vijayvammi Mar 4, 2024
e3a4876
Merge pull request #9 from vijayvammi/one-file
vijayvammi Mar 4, 2024
bfb8a66
fix: bug with parallel execution
vijayvammi Mar 4, 2024
ff9601f
Merge pull request #10 from vijayvammi/one-file
vijayvammi Mar 4, 2024
5d8bd25
feat: SDK function can be a function insted of dotted path
vijayvammi Mar 4, 2024
ace8583
Merge pull request #11 from vijayvammi/issue8-python-function-task
vijayvammi Mar 4, 2024
7775247
docs: fixing docs
vijayvammi Mar 4, 2024
7c00a2d
Merge pull request #12 from vijayvammi/fix_docs
vijayvammi Mar 4, 2024
feaff38
feat: removing re-run from entrypoints
vijayvammi Mar 4, 2024
5a36f6b
feat: retry executor
vijayvammi Mar 7, 2024
e1d296a
Merge pull request #14 from vijayvammi/issue5-create-retry-executor
vijayvammi Mar 7, 2024
3e78a97
docs: updating docs
vijayvammi Mar 7, 2024
8bd9487
ci: no PR check on docs
vijayvammi Mar 7, 2024
4a99b45
Merge pull request #15 from vijayvammi/docs
vijayvammi Mar 7, 2024
e198f2b
docs: updating readme
vijayvammi Mar 7, 2024
1326d3b
Merge pull request #16 from vijayvammi/docs
vijayvammi Mar 7, 2024
c4950a2
docs: updating readme
vijayvammi Mar 7, 2024
0a060ea
docs: updating readme
vijayvammi Mar 7, 2024
1983adf
docs: still working through it
vijayvammi Mar 7, 2024
1416680
fix: getting the json parameter working
vijayvammi Mar 13, 2024
adaed00
feat: Tasks can send back objects now
vijayvammi Mar 14, 2024
2c9b2e4
feat: returns of all tasks is complete
vijayvammi Mar 15, 2024
31f53ca
feat: tasks can return objects
vijayvammi Mar 16, 2024
bc93d8f
Merge pull request #18 from vijayvammi/17-returns-to-be-the-common-si…
vijayvammi Mar 16, 2024
d01a80d
Merge branch 'main' into docs
vijayvammi Mar 16, 2024
5f8afaf
docs: fixing docs
vijayvammi Mar 16, 2024
c86cd42
Merge pull request #19 from vijayvammi/docs
vijayvammi Mar 16, 2024
f39c9d5
fix: fixing parameters
vijayvammi Mar 18, 2024
a2d6629
feat: working parameters
vijayvammi Mar 19, 2024
8138417
feat: working parameters
vijayvammi Mar 20, 2024
09a0ef9
Merge pull request #20 from vijayvammi/parameters
vijayvammi Mar 20, 2024
0c6784a
fix: removing tutorial
vijayvammi Mar 21, 2024
0bc8f15
Merge pull request #21 from vijayvammi/parameters
vijayvammi Mar 21, 2024
bb174a1
fix: removing tutorial
vijayvammi Mar 21, 2024
414557a
Merge pull request #22 from vijayvammi/parameters
vijayvammi Mar 21, 2024
a02ad82
fix: map can return params
vijayvammi Mar 22, 2024
a094919
fix: map can return params
vijayvammi Mar 22, 2024
48ac7b7
Merge pull request #24 from vijayvammi/23-parameters-returned-within-…
vijayvammi Mar 22, 2024
804dcfa
feat: Notebooks can pass objects between themselves
vijayvammi Mar 22, 2024
53e844b
fix: notebooks can return object parameters but cannot consume
vijayvammi Mar 23, 2024
3231e3a
Merge pull request #27 from vijayvammi/26-dill-as-pickler
vijayvammi Mar 23, 2024
3367c19
feat: map nodes and reducer functionality
vijayvammi Mar 25, 2024
bd59132
chore: isort
vijayvammi Mar 25, 2024
f3c791a
chore: isort
vijayvammi Mar 25, 2024
ccd0d05
chore: isort
vijayvammi Mar 25, 2024
8f9c150
chore: isort
vijayvammi Mar 25, 2024
cb74ae0
Merge pull request #28 from vijayvammi/25-reduce-capability-of-a-map-…
vijayvammi Mar 25, 2024
7f74869
fix: examples, sdk execute
vijayvammi Mar 25, 2024
85518c6
Merge pull request #30 from vijayvammi/29-empty-parameters-file-retur…
vijayvammi Mar 25, 2024
7165ccc
docs: working on improving the docs
vijayvammi Mar 27, 2024
da0419f
feat: simpler sdk traversals
vijayvammi Mar 27, 2024
52aa9ca
Merge pull request #33 from vijayvammi/32-sdk-should-have-intuitive-w…
vijayvammi Mar 27, 2024
b575102
Merge branch 'main' into docs
vijayvammi Mar 27, 2024
b669a27
docs: still working on it
vijayvammi Apr 3, 2024
861a717
fix: removing tracker and fixing some bugs
vijayvammi Apr 3, 2024
1a3441b
feat: metrics added
vijayvammi Apr 6, 2024
9cf0bac
feat: Improved logging and presentation
vijayvammi Apr 9, 2024
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
8 changes: 8 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
docs/
.github/
.mypy_cache/
.pytest_cache/
.ruff_cache/
.tox/
.scripts/
.tests/
5 changes: 5 additions & 0 deletions .github/workflows/pr.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
on:
pull_request:
paths-ignore:
- "docs/**"
- "**.md"
- "examples/**"
- "mkdocs.yml"
branches:
- "main"

Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
on:
push:
paths-ignore:
- "docs/**"
- "**.md"
- "examples/**"
branches:
- "main"
- "rc"
Expand Down
2 changes: 0 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -152,5 +152,3 @@ cov.xml
.DS_Store

data/

example_bak/
4 changes: 0 additions & 4 deletions .pylintrc

This file was deleted.

1 change: 1 addition & 0 deletions .python-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.9
121 changes: 69 additions & 52 deletions README.md

Large diffs are not rendered by default.

Binary file removed assets/favicon.png
Binary file not shown.
Binary file removed assets/logo-readme.png
Binary file not shown.
Binary file removed assets/logo.png
Binary file not shown.
Binary file removed assets/work.png
Binary file not shown.
Binary file removed docs/.DS_Store
Binary file not shown.
Binary file added docs/assets/cropped.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed docs/assets/favicon.png
Binary file not shown.
Binary file removed docs/assets/logo.png
Binary file not shown.
Binary file removed docs/assets/logo1.png
Binary file not shown.
Binary file added docs/assets/speed.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/assets/sport.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed docs/assets/whatdo.png
Binary file not shown.
Binary file removed docs/assets/work.png
Binary file not shown.
16 changes: 9 additions & 7 deletions docs/concepts/catalog.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
data between tasks. The default configuration of ```do-nothing``` is no-op by design.
We kindly request to raise a feature request to make us aware of the eco-system.

# TODO: Simplify this

Catalog provides a way to store and retrieve data generated by the individual steps of the dag to downstream
steps of the dag. It can be any storage system that indexes its data by a unique identifier.

Expand All @@ -20,7 +22,7 @@ The directory structure within a partition is the same as the project directory
get/put data in the catalog as if you are working with local directory structure. Every interaction with the catalog
(either by API or configuration) results in an entry in the [```run log```](../concepts/run-log.md/#step_log)

Internally, magnus also uses the catalog to store execution logs of tasks i.e stdout and stderr from
Internally, runnable also uses the catalog to store execution logs of tasks i.e stdout and stderr from
[python](../concepts/task.md/#python) or [shell](../concepts/task.md/#shell) and executed notebook
from [notebook tasks](../concepts/task.md/#notebook).

Expand Down Expand Up @@ -153,7 +155,7 @@ The execution results in the ```catalog``` populated with the artifacts and the
"code_identifier": "6029841c3737fe1163e700b4324d22a469993bb0",
"code_identifier_type": "git",
"code_identifier_dependable": true,
"code_identifier_url": "https://github.com/AstraZeneca/magnus-core.git",
"code_identifier_url": "https://github.com/AstraZeneca/runnable-core.git",
"code_identifier_message": ""
}
],
Expand Down Expand Up @@ -199,7 +201,7 @@ The execution results in the ```catalog``` populated with the artifacts and the
"code_identifier": "6029841c3737fe1163e700b4324d22a469993bb0",
"code_identifier_type": "git",
"code_identifier_dependable": true,
"code_identifier_url": "https://github.com/AstraZeneca/magnus-core.git",
"code_identifier_url": "https://github.com/AstraZeneca/runnable-core.git",
"code_identifier_message": ""
}
],
Expand Down Expand Up @@ -245,7 +247,7 @@ The execution results in the ```catalog``` populated with the artifacts and the
"code_identifier": "6029841c3737fe1163e700b4324d22a469993bb0",
"code_identifier_type": "git",
"code_identifier_dependable": true,
"code_identifier_url": "https://github.com/AstraZeneca/magnus-core.git",
"code_identifier_url": "https://github.com/AstraZeneca/runnable-core.git",
"code_identifier_message": ""
}
],
Expand Down Expand Up @@ -284,7 +286,7 @@ The execution results in the ```catalog``` populated with the artifacts and the
"code_identifier": "6029841c3737fe1163e700b4324d22a469993bb0",
"code_identifier_type": "git",
"code_identifier_dependable": true,
"code_identifier_url": "https://github.com/AstraZeneca/magnus-core.git",
"code_identifier_url": "https://github.com/AstraZeneca/runnable-core.git",
"code_identifier_message": ""
}
],
Expand Down Expand Up @@ -337,7 +339,7 @@ The execution results in the ```catalog``` populated with the artifacts and the
"code_identifier": "6029841c3737fe1163e700b4324d22a469993bb0",
"code_identifier_type": "git",
"code_identifier_dependable": true,
"code_identifier_url": "https://github.com/AstraZeneca/magnus-core.git",
"code_identifier_url": "https://github.com/AstraZeneca/runnable-core.git",
"code_identifier_message": ""
}
],
Expand Down Expand Up @@ -467,7 +469,7 @@ and [notebook](../concepts/task.md/#notebook) tasks.
Data objects can be shared between [python](../concepts/task.md/#python_functions) or
[notebook](../concepts/task.md/#notebook) tasks,
instead of serializing data and deserializing to file structure, using
[get_object](../interactions.md/#magnus.get_object) and [put_object](../interactions.md/#magnus.put_object).
[get_object](../interactions.md/#runnable.get_object) and [put_object](../interactions.md/#runnable.put_object).

Internally, we use [pickle](https:/docs.python.org/3/library/pickle.html) to serialize and
deserialize python objects. Please ensure that the object can be serialized via pickle.
Expand Down
53 changes: 28 additions & 25 deletions docs/concepts/executor.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
Executors are the heart of magnus, they traverse the workflow and execute the tasks within the

## TODO: Simplify

Executors are the heart of runnable, they traverse the workflow and execute the tasks within the
workflow while coordinating with different services
(eg. [run log](../concepts/run-log.md), [catalog](../concepts/catalog.md), [secrets](../concepts/secrets.md) etc)

Expand All @@ -23,7 +26,7 @@ any workflow engine.

## Graph Traversal

In magnus, the graph traversal can be performed by magnus itself or can be handed over to other
In runnable, the graph traversal can be performed by runnable itself or can be handed over to other
orchestration frameworks (e.g Argo workflows, AWS step functions).

### Example
Expand All @@ -44,7 +47,7 @@ translated to argo specification just by changing the configuration.

You can execute the pipeline in default configuration by:

```magnus execute -f examples/concepts/task_shell_simple.yaml```
```runnable execute -f examples/concepts/task_shell_simple.yaml```

``` yaml linenums="1"
--8<-- "examples/configs/default.yaml"
Expand All @@ -60,16 +63,16 @@ translated to argo specification just by changing the configuration.

In this configuration, we are using [argo workflows](https://argoproj.github.io/argo-workflows/)
as our workflow engine. We are also instructing the workflow engine to use a docker image,
```magnus:demo``` defined in line #4, as our execution environment. Please read
```runnable:demo``` defined in line #4, as our execution environment. Please read
[containerised environments](../configurations/executors/container-environments.md) for more information.

Since magnus needs to track the execution status of the workflow, we are using a ```run log```
Since runnable needs to track the execution status of the workflow, we are using a ```run log```
which is persistent and available in for jobs in kubernetes environment.


You can execute the pipeline in argo configuration by:

```magnus execute -f examples/concepts/task_shell_simple.yaml -c examples/configs/argo-config.yaml```
```runnable execute -f examples/concepts/task_shell_simple.yaml -c examples/configs/argo-config.yaml```

``` yaml linenums="1"
--8<-- "examples/configs/argo-config.yaml"
Expand All @@ -78,7 +81,7 @@ translated to argo specification just by changing the configuration.
1. Use argo workflows as the execution engine to run the pipeline.
2. Run this docker image for every step of the pipeline. The docker image should have the same directory structure
as the project directory.
3. Mount the volume from Kubernetes persistent volumes (magnus-volume) to /mnt directory.
3. Mount the volume from Kubernetes persistent volumes (runnable-volume) to /mnt directory.
4. Resource constraints for the container runtime.
5. Since every step runs in a container, the run log should be persisted. Here we are using the file-system as our
run log store.
Expand All @@ -94,20 +97,20 @@ translated to argo specification just by changing the configuration.
- The graph traversal rules follow the the same rules as our workflow. The
step ```success-success-ou7qlf``` in line #15 only happens if the step ```shell-task-dz3l3t```
defined in line #12 succeeds.
- The execution fails if any of the tasks fail. Both argo workflows and magnus ```run log```
- The execution fails if any of the tasks fail. Both argo workflows and runnable ```run log```
mark the execution as failed.


```yaml linenums="1"
apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
generateName: magnus-dag-
generateName: runnable-dag-
annotations: {}
labels: {}
spec:
activeDeadlineSeconds: 172800
entrypoint: magnus-dag
entrypoint: runnable-dag
podGC:
strategy: OnPodCompletion
retryStrategy:
Expand All @@ -119,7 +122,7 @@ translated to argo specification just by changing the configuration.
maxDuration: '3600'
serviceAccountName: default-editor
templates:
- name: magnus-dag
- name: runnable-dag
failFast: true
dag:
tasks:
Expand All @@ -131,9 +134,9 @@ translated to argo specification just by changing the configuration.
depends: shell-task-4jy8pl.Succeeded
- name: shell-task-4jy8pl
container:
image: magnus:demo
image: runnable:demo
command:
- magnus
- runnable
- execute_single_node
- '{{workflow.parameters.run_id}}'
- shell
Expand All @@ -156,9 +159,9 @@ translated to argo specification just by changing the configuration.
cpu: 250m
- name: success-success-djhm6j
container:
image: magnus:demo
image: runnable:demo
command:
- magnus
- runnable
- execute_single_node
- '{{workflow.parameters.run_id}}'
- success
Expand Down Expand Up @@ -189,13 +192,13 @@ translated to argo specification just by changing the configuration.
volumes:
- name: executor-0
persistentVolumeClaim:
claimName: magnus-volume
claimName: runnable-volume


```


As seen from the above example, once a [pipeline is defined in magnus](../concepts/pipeline.md) either via yaml or SDK, we can
As seen from the above example, once a [pipeline is defined in runnable](../concepts/pipeline.md) either via yaml or SDK, we can
run the pipeline in different environments just by providing a different configuration. Most often, there is
no need to change the code or deviate from standard best practices while coding.

Expand All @@ -204,11 +207,11 @@ no need to change the code or deviate from standard best practices while coding.

!!! note

This section is to understand the internal mechanism of magnus and not required if you just want to
This section is to understand the internal mechanism of runnable and not required if you just want to
use different executors.


Independent of traversal, all the tasks are executed within the ```context``` of magnus.
Independent of traversal, all the tasks are executed within the ```context``` of runnable.

A closer look at the actual task implemented as part of transpiled workflow in argo
specification details the inner workings. Below is a snippet of the argo specification from
Expand All @@ -217,9 +220,9 @@ lines 18 to 34.
```yaml linenums="18"
- name: shell-task-dz3l3t
container:
image: magnus-example:latest
image: runnable-example:latest
command:
- magnus
- runnable
- execute_single_node
- '{{workflow.parameters.run_id}}'
- shell
Expand All @@ -235,17 +238,17 @@ lines 18 to 34.
```

The actual ```command``` to run is not the ```command``` defined in the workflow,
i.e ```echo hello world```, but a command in the CLI of magnus which specifies the workflow file,
i.e ```echo hello world```, but a command in the CLI of runnable which specifies the workflow file,
the step name and the configuration file.

### Context of magnus
### Context of runnable

Any ```task``` defined by the user as part of the workflow always runs as a *sub-command* of
magnus. In that sense, magnus follows the
runnable. In that sense, runnable follows the
[decorator pattern](https://en.wikipedia.org/wiki/Decorator_pattern) without being part of the
application codebase.

In a very simplistic sense, the below stubbed-code explains the context of magnus during
In a very simplistic sense, the below stubbed-code explains the context of runnable during
execution of a task.

```python linenums="1"
Expand Down
26 changes: 13 additions & 13 deletions docs/concepts/experiment-tracking.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ during the execution of the pipeline.

=== "Using the API"

The highlighted lines in the below example show how to [use the API](../interactions.md/#magnus.track_this)
The highlighted lines in the below example show how to [use the API](../interactions.md/#runnable.track_this)

Any pydantic model as a value would be dumped as a dict, respecting the alias, before tracking it.

Expand Down Expand Up @@ -61,7 +61,7 @@ during the execution of the pipeline.
"code_identifier": "793b052b8b603760ff1eb843597361219832b61c",
"code_identifier_type": "git",
"code_identifier_dependable": true,
"code_identifier_url": "https://github.com/AstraZeneca/magnus-core.git",
"code_identifier_url": "https://github.com/AstraZeneca/runnable-core.git",
"code_identifier_message": ""
}
],
Expand Down Expand Up @@ -106,7 +106,7 @@ during the execution of the pipeline.
"code_identifier": "793b052b8b603760ff1eb843597361219832b61c",
"code_identifier_type": "git",
"code_identifier_dependable": true,
"code_identifier_url": "https://github.com/AstraZeneca/magnus-core.git",
"code_identifier_url": "https://github.com/AstraZeneca/runnable-core.git",
"code_identifier_message": ""
}
],
Expand Down Expand Up @@ -162,7 +162,7 @@ during the execution of the pipeline.
"start_at": "shell",
"name": "",
"description": "An example pipeline to demonstrate setting experiment tracking metrics\nusing environment variables. Any environment variable with
prefix\n'MAGNUS_TRACK_' will be recorded as a metric captured during the step.\n\nYou can run this pipeline as:\n magnus execute -f
prefix\n'runnable_TRACK_' will be recorded as a metric captured during the step.\n\nYou can run this pipeline as:\n runnable execute -f
examples/concepts/experiment_tracking_env.yaml\n",
"internal_branch_name": "",
"steps": {
Expand Down Expand Up @@ -207,7 +207,7 @@ The step is defaulted to be 0.

=== "Using the API"

The highlighted lines in the below example show how to [use the API](../interactions.md/#magnus.track_this) with
The highlighted lines in the below example show how to [use the API](../interactions.md/#runnable.track_this) with
the step parameter.

You can run this example by ```python run examples/concepts/experiment_tracking_step.py```
Expand Down Expand Up @@ -247,7 +247,7 @@ The step is defaulted to be 0.
"code_identifier": "858c4df44f15d81139341641c63ead45042e0d89",
"code_identifier_type": "git",
"code_identifier_dependable": true,
"code_identifier_url": "https://github.com/AstraZeneca/magnus-core.git",
"code_identifier_url": "https://github.com/AstraZeneca/runnable-core.git",
"code_identifier_message": ""
}
],
Expand Down Expand Up @@ -301,7 +301,7 @@ The step is defaulted to be 0.
"code_identifier": "858c4df44f15d81139341641c63ead45042e0d89",
"code_identifier_type": "git",
"code_identifier_dependable": true,
"code_identifier_url": "https://github.com/AstraZeneca/magnus-core.git",
"code_identifier_url": "https://github.com/AstraZeneca/runnable-core.git",
"code_identifier_message": ""
}
],
Expand Down Expand Up @@ -393,14 +393,14 @@ The step is defaulted to be 0.
!!! note "Opt out"

Pipelines need not use the ```experiment-tracking``` if the preferred tools of choice is
not implemented in magnus. The default configuration of ```do-nothing``` is no-op by design.
not implemented in runnable. The default configuration of ```do-nothing``` is no-op by design.
We kindly request to raise a feature request to make us aware of the eco-system.


The default experiment tracking tool of magnus is a no-op as the ```run log``` captures all the
The default experiment tracking tool of runnable is a no-op as the ```run log``` captures all the
required details. To make it compatible with other experiment tracking tools like
[mlflow](https://mlflow.org/docs/latest/tracking.html) or
[Weights and Biases](https://wandb.ai/site/experiment-tracking), we map attributes of magnus
[Weights and Biases](https://wandb.ai/site/experiment-tracking), we map attributes of runnable
to the underlying tool.

For example, for mlflow:
Expand All @@ -420,7 +420,7 @@ Since mlflow does not support step wise logging of parameters, the key name is f

!!! note inline end "Shortcomings"

Experiment tracking capabilities of magnus are inferior in integration with
Experiment tracking capabilities of runnable are inferior in integration with
popular python frameworks like pytorch and tensorflow as compared to other
experiment tracking tools.

Expand Down Expand Up @@ -453,7 +453,7 @@ Since mlflow does not support step wise logging of parameters, the key name is f

<figure markdown>
![Image](../assets/screenshots/mlflow.png){ width="800" height="600"}
<figcaption>mlflow UI for the execution. The run_id remains the same as the run_id of magnus</figcaption>
<figcaption>mlflow UI for the execution. The run_id remains the same as the run_id of runnable</figcaption>
</figure>

<figure markdown>
Expand All @@ -464,5 +464,5 @@ Since mlflow does not support step wise logging of parameters, the key name is f


To provide implementation specific capabilities, we also provide a
[python API](../interactions.md/#magnus.get_experiment_tracker_context) to obtain the client context. The default
[python API](../interactions.md/#runnable.get_experiment_tracker_context) to obtain the client context. The default
client context is a [null context manager](https://docs.python.org/3/library/contextlib.html#contextlib.nullcontext).
Loading
Loading