koheesio 0.7.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- koheesio/__about__.py +48 -0
- koheesio/__init__.py +37 -0
- koheesio/asyncio/__init__.py +107 -0
- koheesio/asyncio/http.py +383 -0
- koheesio/context.py +480 -0
- koheesio/integrations/__init__.py +3 -0
- koheesio/integrations/box.py +664 -0
- koheesio/integrations/spark/__init__.py +0 -0
- koheesio/integrations/spark/dq/__init__.py +0 -0
- koheesio/integrations/spark/dq/spark_expectations.py +166 -0
- koheesio/integrations/spark/sftp.py +477 -0
- koheesio/logger.py +267 -0
- koheesio/models/__init__.py +668 -0
- koheesio/models/sql.py +68 -0
- koheesio/notifications/__init__.py +26 -0
- koheesio/notifications/slack.py +122 -0
- koheesio/secrets/__init__.py +72 -0
- koheesio/secrets/cerberus.py +89 -0
- koheesio/spark/__init__.py +53 -0
- koheesio/spark/delta.py +311 -0
- koheesio/spark/etl_task.py +141 -0
- koheesio/spark/readers/__init__.py +56 -0
- koheesio/spark/readers/databricks/__init__.py +0 -0
- koheesio/spark/readers/databricks/autoloader.py +116 -0
- koheesio/spark/readers/delta.py +326 -0
- koheesio/spark/readers/dummy.py +44 -0
- koheesio/spark/readers/file_loader.py +233 -0
- koheesio/spark/readers/hana.py +66 -0
- koheesio/spark/readers/jdbc.py +109 -0
- koheesio/spark/readers/kafka.py +138 -0
- koheesio/spark/readers/memory.py +113 -0
- koheesio/spark/readers/metastore.py +21 -0
- koheesio/spark/readers/rest_api.py +126 -0
- koheesio/spark/readers/snowflake.py +33 -0
- koheesio/spark/readers/spark_sql_reader.py +62 -0
- koheesio/spark/readers/teradata.py +72 -0
- koheesio/spark/snowflake.py +1343 -0
- koheesio/spark/transformations/__init__.py +552 -0
- koheesio/spark/transformations/arrays.py +509 -0
- koheesio/spark/transformations/camel_to_snake.py +82 -0
- koheesio/spark/transformations/cast_to_datatype.py +880 -0
- koheesio/spark/transformations/date_time/__init__.py +277 -0
- koheesio/spark/transformations/date_time/interval.py +345 -0
- koheesio/spark/transformations/drop_column.py +50 -0
- koheesio/spark/transformations/dummy.py +38 -0
- koheesio/spark/transformations/get_item.py +138 -0
- koheesio/spark/transformations/hash.py +103 -0
- koheesio/spark/transformations/lookup.py +174 -0
- koheesio/spark/transformations/repartition.py +66 -0
- koheesio/spark/transformations/replace.py +121 -0
- koheesio/spark/transformations/row_number_dedup.py +142 -0
- koheesio/spark/transformations/sql_transform.py +37 -0
- koheesio/spark/transformations/strings/__init__.py +66 -0
- koheesio/spark/transformations/strings/change_case.py +181 -0
- koheesio/spark/transformations/strings/concat.py +129 -0
- koheesio/spark/transformations/strings/pad.py +99 -0
- koheesio/spark/transformations/strings/regexp.py +158 -0
- koheesio/spark/transformations/strings/replace.py +108 -0
- koheesio/spark/transformations/strings/split.py +135 -0
- koheesio/spark/transformations/strings/substring.py +80 -0
- koheesio/spark/transformations/strings/trim.py +133 -0
- koheesio/spark/transformations/transform.py +104 -0
- koheesio/spark/transformations/uuid5.py +166 -0
- koheesio/spark/utils.py +179 -0
- koheesio/spark/writers/__init__.py +79 -0
- koheesio/spark/writers/buffer.py +486 -0
- koheesio/spark/writers/delta/__init__.py +15 -0
- koheesio/spark/writers/delta/batch.py +366 -0
- koheesio/spark/writers/delta/scd.py +577 -0
- koheesio/spark/writers/delta/stream.py +36 -0
- koheesio/spark/writers/delta/utils.py +68 -0
- koheesio/spark/writers/dummy.py +84 -0
- koheesio/spark/writers/kafka.py +180 -0
- koheesio/spark/writers/snowflake.py +5 -0
- koheesio/spark/writers/stream.py +358 -0
- koheesio/sso/__init__.py +0 -0
- koheesio/sso/okta.py +108 -0
- koheesio/steps/__init__.py +685 -0
- koheesio/steps/dummy.py +43 -0
- koheesio/steps/http.py +447 -0
- koheesio/utils.py +102 -0
- koheesio-0.7.0.dist-info/METADATA +238 -0
- koheesio-0.7.0.dist-info/RECORD +85 -0
- koheesio-0.7.0.dist-info/WHEEL +4 -0
- koheesio-0.7.0.dist-info/licenses/LICENSE.txt +201 -0
koheesio/__about__.py
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""A type-safe Python framework for building efficient data pipelines
|
|
2
|
+
|
|
3
|
+
Koheesio is a Python framework for building type-safe and efficient data pipelines, particularly useful for
|
|
4
|
+
large-scale data processing. It promotes modularity, composability, and collaboration, allowing for the creation of
|
|
5
|
+
complex pipelines from simple, reusable components.
|
|
6
|
+
|
|
7
|
+
Leveraging Pydantic, it ensures predictable pipeline execution and structured configurations within pipeline components.
|
|
8
|
+
|
|
9
|
+
With its simple and straightforward API, Koheesio makes it easy for developers to build and manage data pipelines,
|
|
10
|
+
enhancing productivity and code maintainability.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
LICENSE_INFO = "Licensed as Apache 2.0"
|
|
14
|
+
SOURCE = "https://github.com/Nike-Inc/koheesio"
|
|
15
|
+
__version__ = "0.7.0"
|
|
16
|
+
__logo__ = 75, (
|
|
17
|
+
b"\x1f\x8b\x08\x00TiGf\x02\xff}\x91\xbb\r\xc30\x0cD{Nq\x1bh\n\x01\x16R\xa4pK@\x8bh\xf8\xe8\xf8\x89\xe9\x04\xf0\x15"
|
|
18
|
+
b"\xc4\x91\x10\x9f(J`z\xbd4B\xea8J\xf2\xa01T\x02\x01,\x0b\x85Q\x92\x07\xe9\x9cK\x92\xd1,\xe0mRBL\x9c\xa6\x9b\xee"
|
|
19
|
+
b"\xeet)\x07Av\xc9/\x0b\x98\x93\xb4=\xd1v\xa4\xf5NG\xc6\xe5\xce\x93nk\x8d\x81\xf5\xed\x92\x80AmC\xbb\xde,.\x7f\x1fc"
|
|
20
|
+
b"\x0fU\xa79\x19\x82\x16]\x1248\x8f\xa5\x7f\x1c|\x92\xe2\xb8\xa59\xfd\xa5\x86\x8b.I\x9a\xf3\xd4W\x80\x8a\xd3\x9e"
|
|
21
|
+
b"\xfb\xba\\\xecm\x9f#\xee\xea\x92}M+\xffb\xb7\xb2\xc4\xc4K\x88Zui\xda\xedD\xfb\x00\xcfU6\xd3_\x02\x00\x00"
|
|
22
|
+
)
|
|
23
|
+
__short_description__ = __doc__.split("\n", maxsplit=1)[0]
|
|
24
|
+
__about__ = f"""Koheesio - v{__version__}
|
|
25
|
+
{__short_description__}
|
|
26
|
+
{LICENSE_INFO}
|
|
27
|
+
Source: {SOURCE}
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
# fmt: off
|
|
32
|
+
def _about(): # pragma: no cover
|
|
33
|
+
"""Return the Koheesio logo and version/about information as a string
|
|
34
|
+
Note: this code is not meant to be readable, instead it is written to be as compact as possible
|
|
35
|
+
"""
|
|
36
|
+
import gzip
|
|
37
|
+
_w, _logo = __logo__
|
|
38
|
+
_h, _v, _n = "\u2500", "\u2502", "\n"
|
|
39
|
+
return gzip.decompress(_logo).decode() + (
|
|
40
|
+
_n + '\u256d' + _h * _w + "\u256e" + _n +
|
|
41
|
+
f"{_n.join([f'{_v}{_l:^{_w}}{_v}' for _l in __about__.split(_n)[:-1]])}" +
|
|
42
|
+
_n + "\u2570" + _h * _w + "\u256f"
|
|
43
|
+
)
|
|
44
|
+
# fmt: on
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
if __name__ == "__main__":
|
|
48
|
+
print(_about())
|
koheesio/__init__.py
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# pragma: no cover
|
|
2
|
+
from os import environ
|
|
3
|
+
|
|
4
|
+
from koheesio.__about__ import __version__, _about
|
|
5
|
+
from koheesio.context import Context
|
|
6
|
+
from koheesio.logger import LoggingFactory
|
|
7
|
+
from koheesio.models import BaseModel, ExtraParamsMixin
|
|
8
|
+
from koheesio.steps import Step, StepOutput
|
|
9
|
+
from koheesio.utils import convert_str_to_bool
|
|
10
|
+
|
|
11
|
+
_koheesio_print_logo = convert_str_to_bool(environ.get("KOHEESIO__PRINT_LOGO", "True"))
|
|
12
|
+
_logo_printed = False
|
|
13
|
+
ABOUT = _about()
|
|
14
|
+
VERSION = __version__
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"ABOUT",
|
|
18
|
+
"BaseModel",
|
|
19
|
+
"Context",
|
|
20
|
+
"ExtraParamsMixin",
|
|
21
|
+
"LoggingFactory",
|
|
22
|
+
"Step",
|
|
23
|
+
"StepOutput",
|
|
24
|
+
"VERSION",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def print_logo():
|
|
29
|
+
global _logo_printed
|
|
30
|
+
global _koheesio_print_logo
|
|
31
|
+
|
|
32
|
+
if not _logo_printed and _koheesio_print_logo:
|
|
33
|
+
print(ABOUT)
|
|
34
|
+
_logo_printed = True
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
print_logo()
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module provides classes for asynchronous steps in the koheesio package.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Dict, Union
|
|
6
|
+
from abc import ABC
|
|
7
|
+
from asyncio import iscoroutine
|
|
8
|
+
|
|
9
|
+
from koheesio.steps import Step, StepMetaClass, StepOutput
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class AsyncStepMetaClass(StepMetaClass):
|
|
13
|
+
"""Metaclass for asynchronous steps.
|
|
14
|
+
|
|
15
|
+
This metaclass is used to define asynchronous steps in the Koheesio framework.
|
|
16
|
+
It inherits from the StepMetaClass and provides additional functionality for
|
|
17
|
+
executing asynchronous steps.
|
|
18
|
+
|
|
19
|
+
Attributes:
|
|
20
|
+
None
|
|
21
|
+
|
|
22
|
+
Methods:
|
|
23
|
+
_execute_wrapper: Wrapper method for executing asynchronous steps.
|
|
24
|
+
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def _execute_wrapper(cls, *args, **kwargs):
|
|
28
|
+
"""Wrapper method for executing asynchronous steps.
|
|
29
|
+
|
|
30
|
+
This method is called when an asynchronous step is executed. It wraps the
|
|
31
|
+
execution of the step with additional functionality.
|
|
32
|
+
|
|
33
|
+
Parameters
|
|
34
|
+
----------
|
|
35
|
+
*args: Variable length argument list.
|
|
36
|
+
**kwargs: Arbitrary keyword arguments.
|
|
37
|
+
|
|
38
|
+
Returns
|
|
39
|
+
-------
|
|
40
|
+
The result of executing the asynchronous step.
|
|
41
|
+
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
return super()._execute_wrapper(*args, **kwargs)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class AsyncStepOutput(Step.Output):
|
|
48
|
+
"""
|
|
49
|
+
Represents the output of an asynchronous step.
|
|
50
|
+
|
|
51
|
+
This class extends the base `Step.Output` class and provides additional functionality for merging key-value maps.
|
|
52
|
+
|
|
53
|
+
Attributes
|
|
54
|
+
----------
|
|
55
|
+
...
|
|
56
|
+
|
|
57
|
+
Methods
|
|
58
|
+
-------
|
|
59
|
+
merge(other: Union[Dict, StepOutput])
|
|
60
|
+
Merge key-value map with self.
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
def merge(self, other: Union[Dict, StepOutput]):
|
|
64
|
+
"""Merge key,value map with self
|
|
65
|
+
|
|
66
|
+
Examples
|
|
67
|
+
--------
|
|
68
|
+
```python
|
|
69
|
+
step_output = StepOutput(foo="bar")
|
|
70
|
+
step_output.merge({"lorem": "ipsum"}) # step_output will now contain {'foo': 'bar', 'lorem': 'ipsum'}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Functionally similar to adding two dicts together; like running `{**dict_a, **dict_b}`.
|
|
74
|
+
|
|
75
|
+
Parameters
|
|
76
|
+
----------
|
|
77
|
+
other: Union[Dict, StepOutput]
|
|
78
|
+
Dict or another instance of a StepOutputs class that will be added to self
|
|
79
|
+
"""
|
|
80
|
+
if isinstance(other, StepOutput):
|
|
81
|
+
other = other.model_dump() # ensures we really have a dict
|
|
82
|
+
|
|
83
|
+
if not iscoroutine(other):
|
|
84
|
+
for k, v in other.items():
|
|
85
|
+
self.set(k, v)
|
|
86
|
+
|
|
87
|
+
return self
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class AsyncStep(Step, ABC, metaclass=AsyncStepMetaClass):
|
|
91
|
+
"""
|
|
92
|
+
Asynchronous step class that inherits from Step and uses the AsyncStepMetaClass metaclass.
|
|
93
|
+
|
|
94
|
+
Attributes
|
|
95
|
+
----------
|
|
96
|
+
Output : AsyncStepOutput
|
|
97
|
+
The output class for the asynchronous step.
|
|
98
|
+
"""
|
|
99
|
+
|
|
100
|
+
class Output(AsyncStepOutput):
|
|
101
|
+
"""
|
|
102
|
+
Output class for asyncio step.
|
|
103
|
+
|
|
104
|
+
This class represents the output of the asyncio step. It inherits from the AsyncStepOutput class.
|
|
105
|
+
"""
|
|
106
|
+
|
|
107
|
+
__output__: Output
|
koheesio/asyncio/http.py
ADDED
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module contains async implementation of HTTP step.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import asyncio
|
|
8
|
+
import warnings
|
|
9
|
+
from typing import Any, Dict, List, Optional, Tuple, Union
|
|
10
|
+
|
|
11
|
+
import nest_asyncio
|
|
12
|
+
import yarl
|
|
13
|
+
from aiohttp import BaseConnector, ClientSession, TCPConnector
|
|
14
|
+
from aiohttp_retry import ExponentialRetry, RetryClient, RetryOptionsBase
|
|
15
|
+
|
|
16
|
+
from pydantic import Field, SecretStr, field_validator, model_validator
|
|
17
|
+
|
|
18
|
+
from koheesio.asyncio import AsyncStep, AsyncStepOutput
|
|
19
|
+
from koheesio.models import ExtraParamsMixin
|
|
20
|
+
from koheesio.steps.http import HttpMethod
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class AsyncHttpStep(AsyncStep, ExtraParamsMixin):
|
|
24
|
+
"""
|
|
25
|
+
Asynchronous HTTP step for making HTTP requests using aiohttp.
|
|
26
|
+
|
|
27
|
+
Parameters
|
|
28
|
+
----------
|
|
29
|
+
client_session : Optional[ClientSession]
|
|
30
|
+
Aiohttp ClientSession.
|
|
31
|
+
url : List[yarl.URL]
|
|
32
|
+
List of yarl.URL.
|
|
33
|
+
retry_options : Optional[RetryOptionsBase]
|
|
34
|
+
Retry options for the request.
|
|
35
|
+
connector : Optional[BaseConnector]
|
|
36
|
+
Connector for the aiohttp request.
|
|
37
|
+
headers : Optional[Dict[str, Union[str, SecretStr]]]
|
|
38
|
+
Request headers.
|
|
39
|
+
|
|
40
|
+
Output
|
|
41
|
+
------
|
|
42
|
+
responses_urls : Optional[List[Tuple[Dict[str, Any], yarl.URL]]]
|
|
43
|
+
List of responses from the API and request URL.
|
|
44
|
+
|
|
45
|
+
Examples
|
|
46
|
+
--------
|
|
47
|
+
```python
|
|
48
|
+
>>> import asyncio
|
|
49
|
+
>>> from aiohttp import ClientSession
|
|
50
|
+
>>> from aiohttp.connector import TCPConnector
|
|
51
|
+
>>> from aiohttp_retry import ExponentialRetry
|
|
52
|
+
>>> from koheesio.steps.async.http import AsyncHttpStep
|
|
53
|
+
>>> from yarl import URL
|
|
54
|
+
>>> from typing import Dict, Any, Union, List, Tuple
|
|
55
|
+
>>>
|
|
56
|
+
>>> # Initialize the AsyncHttpStep
|
|
57
|
+
>>> async def main():
|
|
58
|
+
>>> session = ClientSession()
|
|
59
|
+
>>> urls = [URL('https://example.com/api/1'), URL('https://example.com/api/2')]
|
|
60
|
+
>>> retry_options = ExponentialRetry()
|
|
61
|
+
>>> connector = TCPConnector(limit=10)
|
|
62
|
+
>>> headers = {'Content-Type': 'application/json'}
|
|
63
|
+
>>> step = AsyncHttpStep(
|
|
64
|
+
>>> client_session=session,
|
|
65
|
+
>>> url=urls,
|
|
66
|
+
>>> retry_options=retry_options,
|
|
67
|
+
>>> connector=connector,
|
|
68
|
+
>>> headers=headers
|
|
69
|
+
>>> )
|
|
70
|
+
>>>
|
|
71
|
+
>>> # Execute the step
|
|
72
|
+
>>> responses_urls= await step.get()
|
|
73
|
+
>>>
|
|
74
|
+
>>> return responses_urls
|
|
75
|
+
>>>
|
|
76
|
+
>>> # Run the main function
|
|
77
|
+
>>> responses_urls = asyncio.run(main())
|
|
78
|
+
```
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
client_session: Optional[ClientSession] = Field(default=None, description="Aiohttp ClientSession", exclude=True)
|
|
82
|
+
url: List[yarl.URL] = Field(
|
|
83
|
+
default=None,
|
|
84
|
+
alias="urls",
|
|
85
|
+
description="""Expecting list, as there is no value in executing async request for one value.
|
|
86
|
+
yarl.URL is preferable, because params/data can be injected into URL instance""",
|
|
87
|
+
exclude=True,
|
|
88
|
+
)
|
|
89
|
+
retry_options: Optional[RetryOptionsBase] = Field(
|
|
90
|
+
default=None, description="Retry options for the request", exclude=True
|
|
91
|
+
)
|
|
92
|
+
connector: Optional[BaseConnector] = Field(
|
|
93
|
+
default=None, description="Connector for the aiohttp request", exclude=True
|
|
94
|
+
)
|
|
95
|
+
headers: Dict[str, Union[str, SecretStr]] = Field(
|
|
96
|
+
default_factory=dict,
|
|
97
|
+
description="Request headers",
|
|
98
|
+
alias="header",
|
|
99
|
+
exclude=True,
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
timeout: None = Field(default=None, description="[Optional] Request timeout")
|
|
103
|
+
|
|
104
|
+
method: Union[str, HttpMethod] = Field(
|
|
105
|
+
default=HttpMethod.GET,
|
|
106
|
+
description="What type of Http call to perform. One of 'get', 'post', 'put', 'delete'. Defaults to 'get'.",
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
class Output(AsyncStepOutput):
|
|
110
|
+
"""Output class for Step"""
|
|
111
|
+
|
|
112
|
+
responses_urls: Optional[List[Tuple[Dict[str, Any], yarl.URL]]] = Field(
|
|
113
|
+
default=None, description="List of responses from the API and request URL", repr=False
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
def __tasks_generator(self, method) -> List[asyncio.Task]:
|
|
117
|
+
"""
|
|
118
|
+
Generate a list of tasks for making HTTP requests.
|
|
119
|
+
|
|
120
|
+
Parameters
|
|
121
|
+
----------
|
|
122
|
+
method : HttpMethod
|
|
123
|
+
The HTTP method to use for the requests.
|
|
124
|
+
|
|
125
|
+
Returns
|
|
126
|
+
-------
|
|
127
|
+
List[Task]
|
|
128
|
+
A list of asyncio tasks for making HTTP requests.
|
|
129
|
+
"""
|
|
130
|
+
tasks = [
|
|
131
|
+
asyncio.create_task(
|
|
132
|
+
self.request(
|
|
133
|
+
method=method,
|
|
134
|
+
url=u,
|
|
135
|
+
headers=self.get_headers(),
|
|
136
|
+
)
|
|
137
|
+
)
|
|
138
|
+
for u in self.url
|
|
139
|
+
]
|
|
140
|
+
|
|
141
|
+
return tasks
|
|
142
|
+
|
|
143
|
+
@model_validator(mode="after")
|
|
144
|
+
def _move_extra_params_to_params(self):
|
|
145
|
+
"""
|
|
146
|
+
Move extra_params to params dict.
|
|
147
|
+
|
|
148
|
+
Returns
|
|
149
|
+
-------
|
|
150
|
+
AsyncHttpStep
|
|
151
|
+
The updated AsyncHttpStep instance.
|
|
152
|
+
"""
|
|
153
|
+
return self
|
|
154
|
+
|
|
155
|
+
async def _execute(self, tasks: List[asyncio.Task]) -> List[Tuple[Dict[str, Any], yarl.URL]]:
|
|
156
|
+
"""
|
|
157
|
+
Execute the HTTP requests asynchronously.
|
|
158
|
+
|
|
159
|
+
Parameters
|
|
160
|
+
----------
|
|
161
|
+
tasks : List[Task]
|
|
162
|
+
A list of asyncio tasks for making HTTP requests.
|
|
163
|
+
|
|
164
|
+
Returns
|
|
165
|
+
-------
|
|
166
|
+
List[Tuple[Dict[str, Any], yarl.URL]]
|
|
167
|
+
A list of response data and corresponding request URLs.
|
|
168
|
+
"""
|
|
169
|
+
self._init_session()
|
|
170
|
+
try:
|
|
171
|
+
responses_urls = await asyncio.gather(*tasks)
|
|
172
|
+
finally:
|
|
173
|
+
await self.client_session.close()
|
|
174
|
+
await self.__retry_client.close()
|
|
175
|
+
|
|
176
|
+
return responses_urls
|
|
177
|
+
|
|
178
|
+
def _init_session(self):
|
|
179
|
+
"""
|
|
180
|
+
Initialize the aiohttp session and retry client.
|
|
181
|
+
"""
|
|
182
|
+
self.connector = self.connector or TCPConnector(limit=10)
|
|
183
|
+
self.client_session = self.client_session or ClientSession(connector=self.connector)
|
|
184
|
+
self.retry_options = self.retry_options or ExponentialRetry()
|
|
185
|
+
# Disable pylint warning: attribute is not initialized in __init__
|
|
186
|
+
# pylint: disable=W0201
|
|
187
|
+
self.__retry_client = RetryClient(
|
|
188
|
+
client_session=self.client_session, logger=self.log, retry_options=self.retry_options, raise_for_status=True
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
@field_validator("timeout")
|
|
192
|
+
def validate_timeout(cls, timeout):
|
|
193
|
+
"""
|
|
194
|
+
Validate the 'data' field.
|
|
195
|
+
|
|
196
|
+
Parameters
|
|
197
|
+
----------
|
|
198
|
+
data : Any
|
|
199
|
+
The value of the 'timeout' field.
|
|
200
|
+
|
|
201
|
+
Raises
|
|
202
|
+
------
|
|
203
|
+
ValueError
|
|
204
|
+
If 'data' is not allowed in AsyncHttpStep.
|
|
205
|
+
"""
|
|
206
|
+
if timeout:
|
|
207
|
+
raise ValueError("timeout is not allowed in AsyncHttpStep. Provide timeout through retry_options.")
|
|
208
|
+
|
|
209
|
+
def get_headers(self):
|
|
210
|
+
"""
|
|
211
|
+
Get the request headers.
|
|
212
|
+
|
|
213
|
+
Returns
|
|
214
|
+
-------
|
|
215
|
+
Optional[Dict[str, Union[str, SecretStr]]]
|
|
216
|
+
The request headers.
|
|
217
|
+
"""
|
|
218
|
+
_headers = None
|
|
219
|
+
|
|
220
|
+
if self.headers:
|
|
221
|
+
_headers = {k: v.get_secret_value() if isinstance(v, SecretStr) else v for k, v in self.headers.items()}
|
|
222
|
+
|
|
223
|
+
for k, v in self.headers.items():
|
|
224
|
+
if isinstance(v, SecretStr):
|
|
225
|
+
self.headers[k] = v.get_secret_value()
|
|
226
|
+
|
|
227
|
+
return _headers or self.headers
|
|
228
|
+
|
|
229
|
+
def set_outputs(self, response):
|
|
230
|
+
"""
|
|
231
|
+
Set the outputs of the step.
|
|
232
|
+
|
|
233
|
+
Parameters
|
|
234
|
+
----------
|
|
235
|
+
response : Any
|
|
236
|
+
The response data.
|
|
237
|
+
"""
|
|
238
|
+
warnings.warn("set outputs is not implemented in AsyncHttpStep.")
|
|
239
|
+
|
|
240
|
+
def get_options(self):
|
|
241
|
+
"""
|
|
242
|
+
Get the options of the step.
|
|
243
|
+
"""
|
|
244
|
+
warnings.warn("get_options is not implemented in AsyncHttpStep.")
|
|
245
|
+
|
|
246
|
+
# Disable pylint warning: method was expected to be 'non-async'
|
|
247
|
+
# pylint: disable=W0236
|
|
248
|
+
async def request(
|
|
249
|
+
self,
|
|
250
|
+
method: HttpMethod,
|
|
251
|
+
url: yarl.URL,
|
|
252
|
+
**kwargs,
|
|
253
|
+
) -> Tuple[Dict[str, Any], yarl.URL]:
|
|
254
|
+
"""
|
|
255
|
+
Make an HTTP request.
|
|
256
|
+
|
|
257
|
+
Parameters
|
|
258
|
+
----------
|
|
259
|
+
method : HttpMethod
|
|
260
|
+
The HTTP method to use for the request.
|
|
261
|
+
url : yarl.URL
|
|
262
|
+
The URL to make the request to.
|
|
263
|
+
kwargs : Any
|
|
264
|
+
Additional keyword arguments to pass to the request.
|
|
265
|
+
|
|
266
|
+
Returns
|
|
267
|
+
-------
|
|
268
|
+
Tuple[Dict[str, Any], yarl.URL]
|
|
269
|
+
A tuple containing the response data and the request URL.
|
|
270
|
+
"""
|
|
271
|
+
async with self.__retry_client.request(method=method, url=url, **kwargs) as response:
|
|
272
|
+
res = await response.json()
|
|
273
|
+
|
|
274
|
+
return (res, response.request_info.url)
|
|
275
|
+
|
|
276
|
+
# Disable pylint warning: method was expected to be 'non-async'
|
|
277
|
+
# pylint: disable=W0236
|
|
278
|
+
async def get(self) -> List[Tuple[Dict[str, Any], yarl.URL]]:
|
|
279
|
+
"""
|
|
280
|
+
Make GET requests.
|
|
281
|
+
|
|
282
|
+
Returns
|
|
283
|
+
-------
|
|
284
|
+
List[Tuple[Dict[str, Any], yarl.URL]]
|
|
285
|
+
A list of response data and corresponding request URLs.
|
|
286
|
+
"""
|
|
287
|
+
tasks = self.__tasks_generator(method=HttpMethod.GET)
|
|
288
|
+
responses_urls = await self._execute(tasks=tasks)
|
|
289
|
+
|
|
290
|
+
return responses_urls
|
|
291
|
+
|
|
292
|
+
# Disable pylint warning: method was expected to be 'non-async'
|
|
293
|
+
# pylint: disable=W0236
|
|
294
|
+
async def post(self) -> List[Tuple[Dict[str, Any], yarl.URL]]:
|
|
295
|
+
"""
|
|
296
|
+
Make POST requests.
|
|
297
|
+
|
|
298
|
+
Returns
|
|
299
|
+
-------
|
|
300
|
+
List[Tuple[Dict[str, Any], yarl.URL]]
|
|
301
|
+
A list of response data and corresponding request URLs.
|
|
302
|
+
"""
|
|
303
|
+
tasks = self.__tasks_generator(method=HttpMethod.POST)
|
|
304
|
+
responses_urls = await self._execute(tasks=tasks)
|
|
305
|
+
|
|
306
|
+
return responses_urls
|
|
307
|
+
|
|
308
|
+
# Disable pylint warning: method was expected to be 'non-async'
|
|
309
|
+
# pylint: disable=W0236
|
|
310
|
+
async def put(self) -> List[Tuple[Dict[str, Any], yarl.URL]]:
|
|
311
|
+
"""
|
|
312
|
+
Make PUT requests.
|
|
313
|
+
|
|
314
|
+
Returns
|
|
315
|
+
-------
|
|
316
|
+
List[Tuple[Dict[str, Any], yarl.URL]]
|
|
317
|
+
A list of response data and corresponding request URLs.
|
|
318
|
+
"""
|
|
319
|
+
tasks = self.__tasks_generator(method=HttpMethod.PUT)
|
|
320
|
+
responses_urls = await self._execute(tasks=tasks)
|
|
321
|
+
|
|
322
|
+
return responses_urls
|
|
323
|
+
|
|
324
|
+
# Disable pylint warning: method was expected to be 'non-async'
|
|
325
|
+
# pylint: disable=W0236
|
|
326
|
+
async def delete(self) -> List[Tuple[Dict[str, Any], yarl.URL]]:
|
|
327
|
+
"""
|
|
328
|
+
Make DELETE requests.
|
|
329
|
+
|
|
330
|
+
Returns
|
|
331
|
+
-------
|
|
332
|
+
List[Tuple[Dict[str, Any], yarl.URL]]
|
|
333
|
+
A list of response data and corresponding request URLs.
|
|
334
|
+
"""
|
|
335
|
+
tasks = self.__tasks_generator(method=HttpMethod.DELETE)
|
|
336
|
+
responses_urls = await self._execute(tasks=tasks)
|
|
337
|
+
|
|
338
|
+
return responses_urls
|
|
339
|
+
|
|
340
|
+
def execute(self) -> AsyncHttpStep.Output:
|
|
341
|
+
"""
|
|
342
|
+
Execute the step.
|
|
343
|
+
|
|
344
|
+
Raises
|
|
345
|
+
------
|
|
346
|
+
ValueError
|
|
347
|
+
If the specified HTTP method is not implemented in AsyncHttpStep.
|
|
348
|
+
"""
|
|
349
|
+
# By design asyncio does not allow its event loop to be nested. This presents a practical problem:
|
|
350
|
+
# When in an environment where the event loop is already running
|
|
351
|
+
# it’s impossible to run tasks and wait for the result.
|
|
352
|
+
# Trying to do so will give the error “RuntimeError: This event loop is already running”.
|
|
353
|
+
# The issue pops up in various environments, such as web servers, GUI applications and in
|
|
354
|
+
# Jupyter/DataBricks notebooks.
|
|
355
|
+
nest_asyncio.apply()
|
|
356
|
+
|
|
357
|
+
map_method_func = {
|
|
358
|
+
HttpMethod.GET: self.get,
|
|
359
|
+
HttpMethod.POST: self.post,
|
|
360
|
+
HttpMethod.PUT: self.put,
|
|
361
|
+
HttpMethod.DELETE: self.delete,
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
if self.method not in map_method_func:
|
|
365
|
+
raise ValueError(f"Method {self.method} not implemented in AsyncHttpStep.")
|
|
366
|
+
|
|
367
|
+
self.output.responses_urls = asyncio.run(map_method_func[self.method]())
|
|
368
|
+
|
|
369
|
+
return self.output
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
class AsyncHttpGetStep(AsyncHttpStep):
|
|
373
|
+
"""
|
|
374
|
+
Represents an asynchronous HTTP GET step.
|
|
375
|
+
|
|
376
|
+
This class inherits from the AsyncHttpStep class and specifies the HTTP method as GET.
|
|
377
|
+
|
|
378
|
+
Attributes:
|
|
379
|
+
method (HttpMethod): The HTTP method for the step, set to HttpMethod.GET.
|
|
380
|
+
|
|
381
|
+
"""
|
|
382
|
+
|
|
383
|
+
method: HttpMethod = HttpMethod.GET
|