pyoaev 1.18.20__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.
- docs/conf.py +65 -0
- pyoaev/__init__.py +26 -0
- pyoaev/_version.py +6 -0
- pyoaev/apis/__init__.py +20 -0
- pyoaev/apis/attack_pattern.py +28 -0
- pyoaev/apis/collector.py +29 -0
- pyoaev/apis/cve.py +18 -0
- pyoaev/apis/document.py +29 -0
- pyoaev/apis/endpoint.py +38 -0
- pyoaev/apis/inject.py +29 -0
- pyoaev/apis/inject_expectation/__init__.py +1 -0
- pyoaev/apis/inject_expectation/inject_expectation.py +118 -0
- pyoaev/apis/inject_expectation/model/__init__.py +7 -0
- pyoaev/apis/inject_expectation/model/expectation.py +173 -0
- pyoaev/apis/inject_expectation_trace.py +36 -0
- pyoaev/apis/injector.py +26 -0
- pyoaev/apis/injector_contract.py +56 -0
- pyoaev/apis/inputs/__init__.py +0 -0
- pyoaev/apis/inputs/search.py +72 -0
- pyoaev/apis/kill_chain_phase.py +22 -0
- pyoaev/apis/me.py +17 -0
- pyoaev/apis/organization.py +11 -0
- pyoaev/apis/payload.py +27 -0
- pyoaev/apis/security_platform.py +33 -0
- pyoaev/apis/tag.py +19 -0
- pyoaev/apis/team.py +25 -0
- pyoaev/apis/user.py +31 -0
- pyoaev/backends/__init__.py +14 -0
- pyoaev/backends/backend.py +136 -0
- pyoaev/backends/protocol.py +32 -0
- pyoaev/base.py +320 -0
- pyoaev/client.py +596 -0
- pyoaev/configuration/__init__.py +3 -0
- pyoaev/configuration/configuration.py +188 -0
- pyoaev/configuration/sources.py +44 -0
- pyoaev/contracts/__init__.py +5 -0
- pyoaev/contracts/contract_builder.py +44 -0
- pyoaev/contracts/contract_config.py +292 -0
- pyoaev/contracts/contract_utils.py +22 -0
- pyoaev/contracts/variable_helper.py +124 -0
- pyoaev/daemons/__init__.py +4 -0
- pyoaev/daemons/base_daemon.py +131 -0
- pyoaev/daemons/collector_daemon.py +91 -0
- pyoaev/exceptions.py +219 -0
- pyoaev/helpers.py +451 -0
- pyoaev/mixins.py +242 -0
- pyoaev/signatures/__init__.py +0 -0
- pyoaev/signatures/signature_match.py +12 -0
- pyoaev/signatures/signature_type.py +51 -0
- pyoaev/signatures/types.py +17 -0
- pyoaev/utils.py +211 -0
- pyoaev-1.18.20.dist-info/METADATA +134 -0
- pyoaev-1.18.20.dist-info/RECORD +72 -0
- pyoaev-1.18.20.dist-info/WHEEL +5 -0
- pyoaev-1.18.20.dist-info/licenses/LICENSE +201 -0
- pyoaev-1.18.20.dist-info/top_level.txt +4 -0
- scripts/release.py +127 -0
- test/__init__.py +0 -0
- test/apis/__init__.py +0 -0
- test/apis/expectation/__init__.py +0 -0
- test/apis/expectation/test_expectation.py +338 -0
- test/apis/injector_contract/__init__.py +0 -0
- test/apis/injector_contract/test_injector_contract.py +58 -0
- test/configuration/__init__.py +0 -0
- test/configuration/test_configuration.py +257 -0
- test/configuration/test_sources.py +69 -0
- test/daemons/__init__.py +0 -0
- test/daemons/test_base_daemon.py +109 -0
- test/daemons/test_collector_daemon.py +39 -0
- test/signatures/__init__.py +0 -0
- test/signatures/test_signature_match.py +25 -0
- test/signatures/test_signature_type.py +57 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
from typing import Any, Dict
|
|
2
|
+
|
|
3
|
+
from pyoaev import exceptions as exc
|
|
4
|
+
from pyoaev.apis.inputs.search import InjectorContractSearchPaginationInput
|
|
5
|
+
from pyoaev.base import RESTManager, RESTObject
|
|
6
|
+
from pyoaev.mixins import CreateMixin, DeleteMixin, UpdateMixin
|
|
7
|
+
from pyoaev.utils import RequiredOptional
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class InjectorContract(RESTObject):
|
|
11
|
+
pass
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class InjectorContractManager(CreateMixin, UpdateMixin, DeleteMixin, RESTManager):
|
|
15
|
+
_path = "/injector_contracts"
|
|
16
|
+
_obj_cls = InjectorContract
|
|
17
|
+
_create_attrs = RequiredOptional(
|
|
18
|
+
required=(
|
|
19
|
+
"contract_content",
|
|
20
|
+
"contract_id",
|
|
21
|
+
"contract_labels",
|
|
22
|
+
"injector_id",
|
|
23
|
+
),
|
|
24
|
+
optional=(
|
|
25
|
+
"contract_attack_patterns_ids",
|
|
26
|
+
"contract_attack_patterns_external_ids",
|
|
27
|
+
"contract_vulnerability_external_ids",
|
|
28
|
+
"contract_manual",
|
|
29
|
+
"contract_platforms",
|
|
30
|
+
"external_contract_id",
|
|
31
|
+
"is_atomic_testing",
|
|
32
|
+
),
|
|
33
|
+
)
|
|
34
|
+
_update_attrs = RequiredOptional(
|
|
35
|
+
required=(
|
|
36
|
+
"contract_content",
|
|
37
|
+
"contract_labels",
|
|
38
|
+
),
|
|
39
|
+
optional=(
|
|
40
|
+
"contract_attack_patterns_ids",
|
|
41
|
+
"contract_vulnerability_ids",
|
|
42
|
+
"contract_vulnerability_external_ids",
|
|
43
|
+
"contract_manual",
|
|
44
|
+
"contract_platforms",
|
|
45
|
+
"is_atomic_testing",
|
|
46
|
+
),
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
@exc.on_http_error(exc.OpenAEVUpdateError)
|
|
50
|
+
def search(
|
|
51
|
+
self, input: InjectorContractSearchPaginationInput, **kwargs: Any
|
|
52
|
+
) -> Dict[str, Any]:
|
|
53
|
+
path = f"{self.path}/search"
|
|
54
|
+
# force the serialisation here since we only need a naive serialisation to json
|
|
55
|
+
result = self.openaev.http_post(path, post_data=input.to_dict(), **kwargs)
|
|
56
|
+
return result
|
|
File without changes
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
from typing import Any, Dict, List
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Filter:
|
|
5
|
+
def __init__(self, key: str, mode: str, operator: str, values: List[str]):
|
|
6
|
+
self.key = key
|
|
7
|
+
self.mode = mode
|
|
8
|
+
self.operator = operator
|
|
9
|
+
self.values = values
|
|
10
|
+
|
|
11
|
+
def to_dict(self) -> dict[str, Any]:
|
|
12
|
+
return {k: v for k, v in self.__dict__.items() if v is not None}
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class FilterGroup:
|
|
16
|
+
def __init__(self, mode: str, filters: List[Filter]):
|
|
17
|
+
self.mode = mode
|
|
18
|
+
self.filters = filters
|
|
19
|
+
|
|
20
|
+
def to_dict(self) -> dict[str, Any]:
|
|
21
|
+
dictionary: dict[str, Any] = {"mode": self.mode}
|
|
22
|
+
if self.filters:
|
|
23
|
+
filter_dicts: List[dict[str, Any]] = []
|
|
24
|
+
for filter_ in self.filters:
|
|
25
|
+
filter_dicts.append(filter_.to_dict())
|
|
26
|
+
dictionary["filters"] = filter_dicts
|
|
27
|
+
return dictionary
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class SearchPaginationInput:
|
|
31
|
+
def __init__(
|
|
32
|
+
self,
|
|
33
|
+
page: int,
|
|
34
|
+
size: int,
|
|
35
|
+
filter_group: FilterGroup,
|
|
36
|
+
text_search: str,
|
|
37
|
+
sorts: Dict[str, str],
|
|
38
|
+
):
|
|
39
|
+
self.size = size
|
|
40
|
+
self.page = page
|
|
41
|
+
self.filterGroup = filter_group
|
|
42
|
+
self.text_search = text_search
|
|
43
|
+
self.sorts = sorts
|
|
44
|
+
|
|
45
|
+
def to_dict(self) -> dict[str, Any]:
|
|
46
|
+
dictionary: dict[str, Any] = {"page": self.page, "size": self.size}
|
|
47
|
+
if self.sorts:
|
|
48
|
+
dictionary["sorts"] = self.sorts
|
|
49
|
+
if self.text_search:
|
|
50
|
+
dictionary["textSearch"] = self.text_search
|
|
51
|
+
if self.filterGroup:
|
|
52
|
+
dictionary["filterGroup"] = self.filterGroup.to_dict()
|
|
53
|
+
return dictionary
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class InjectorContractSearchPaginationInput(SearchPaginationInput):
|
|
57
|
+
def __init__(
|
|
58
|
+
self,
|
|
59
|
+
page: int,
|
|
60
|
+
size: int,
|
|
61
|
+
filter_group: FilterGroup,
|
|
62
|
+
text_search: str = None,
|
|
63
|
+
sorts: Dict[str, str] = None,
|
|
64
|
+
include_full_details: bool = True,
|
|
65
|
+
):
|
|
66
|
+
super().__init__(page, size, filter_group, text_search, sorts)
|
|
67
|
+
self.include_full_details = include_full_details
|
|
68
|
+
|
|
69
|
+
def to_dict(self) -> dict[str, Any]:
|
|
70
|
+
dictionary: dict[str, Any] = super().to_dict()
|
|
71
|
+
dictionary["include_full_details"] = self.include_full_details
|
|
72
|
+
return dictionary
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from typing import Any, Dict, List
|
|
2
|
+
|
|
3
|
+
from pyoaev import exceptions as exc
|
|
4
|
+
from pyoaev.base import RESTManager, RESTObject
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class KillChainPhase(RESTObject):
|
|
8
|
+
_id_attr = "phase_id"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class KillChainPhaseManager(RESTManager):
|
|
12
|
+
_path = "/kill_chain_phases"
|
|
13
|
+
_obj_cls = KillChainPhase
|
|
14
|
+
|
|
15
|
+
@exc.on_http_error(exc.OpenAEVUpdateError)
|
|
16
|
+
def upsert(
|
|
17
|
+
self, kill_chain_phases: List[Dict[str, Any]], **kwargs: Any
|
|
18
|
+
) -> Dict[str, Any]:
|
|
19
|
+
data = {"kill_chain_phases": kill_chain_phases}
|
|
20
|
+
path = f"{self.path}/upsert"
|
|
21
|
+
result = self.openaev.http_post(path, post_data=data, **kwargs)
|
|
22
|
+
return result
|
pyoaev/apis/me.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from typing import Any, cast
|
|
2
|
+
|
|
3
|
+
from pyoaev.base import RESTManager, RESTObject
|
|
4
|
+
from pyoaev.mixins import GetWithoutIdMixin, UpdateMixin
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Me(RESTObject):
|
|
8
|
+
_id_attr = None
|
|
9
|
+
_repr_attr = "user_email"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class MeManager(GetWithoutIdMixin, UpdateMixin, RESTManager):
|
|
13
|
+
_path = "/me"
|
|
14
|
+
_obj_cls = Me
|
|
15
|
+
|
|
16
|
+
def get(self, **kwargs: Any) -> Me:
|
|
17
|
+
return cast(Me, super().get(**kwargs))
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from pyoaev.base import RESTManager, RESTObject
|
|
2
|
+
from pyoaev.mixins import ListMixin, UpdateMixin
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class Organization(RESTObject):
|
|
6
|
+
pass
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class OrganizationManager(ListMixin, UpdateMixin, RESTManager):
|
|
10
|
+
_path = "/organizations"
|
|
11
|
+
_obj_cls = Organization
|
pyoaev/apis/payload.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from typing import Any, Dict
|
|
2
|
+
|
|
3
|
+
from pyoaev import exceptions as exc
|
|
4
|
+
from pyoaev.base import RESTManager, RESTObject
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Payload(RESTObject):
|
|
8
|
+
_id_attr = "payload_id"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class PayloadManager(RESTManager):
|
|
12
|
+
_path = "/payloads"
|
|
13
|
+
_obj_cls = Payload
|
|
14
|
+
|
|
15
|
+
@exc.on_http_error(exc.OpenAEVUpdateError)
|
|
16
|
+
def upsert(self, payload: Dict[str, Any], **kwargs: Any) -> Dict[str, Any]:
|
|
17
|
+
path = f"{self.path}/upsert"
|
|
18
|
+
result = self.openaev.http_post(path, post_data=payload, **kwargs)
|
|
19
|
+
return result
|
|
20
|
+
|
|
21
|
+
@exc.on_http_error(exc.OpenAEVUpdateError)
|
|
22
|
+
def deprecate(
|
|
23
|
+
self, payloads_processed: Dict[str, Any], **kwargs: Any
|
|
24
|
+
) -> Dict[str, Any]:
|
|
25
|
+
path = f"{self.path}/deprecate"
|
|
26
|
+
result = self.openaev.http_post(path, post_data=payloads_processed, **kwargs)
|
|
27
|
+
return result
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from typing import Any, Dict
|
|
2
|
+
|
|
3
|
+
from pyoaev import exceptions as exc
|
|
4
|
+
from pyoaev.base import RESTManager, RESTObject
|
|
5
|
+
from pyoaev.mixins import CreateMixin, GetMixin, ListMixin, UpdateMixin
|
|
6
|
+
from pyoaev.utils import RequiredOptional
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class SecurityPlatform(RESTObject):
|
|
10
|
+
pass
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class SecurityPlatformManager(
|
|
14
|
+
GetMixin, ListMixin, CreateMixin, UpdateMixin, RESTManager
|
|
15
|
+
):
|
|
16
|
+
_path = "/security_platforms"
|
|
17
|
+
_obj_cls = SecurityPlatform
|
|
18
|
+
_create_attrs = RequiredOptional(
|
|
19
|
+
required=("asset_name", "security_platform_type"),
|
|
20
|
+
optional=(
|
|
21
|
+
"asset_description",
|
|
22
|
+
"security_platform_logo_light",
|
|
23
|
+
"security_platform_logo_dark",
|
|
24
|
+
),
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
@exc.on_http_error(exc.OpenAEVUpdateError)
|
|
28
|
+
def upsert(
|
|
29
|
+
self, security_platform: Dict[str, Any], **kwargs: Any
|
|
30
|
+
) -> Dict[str, Any]:
|
|
31
|
+
path = f"{self.path}/upsert"
|
|
32
|
+
result = self.openaev.http_post(path, post_data=security_platform, **kwargs)
|
|
33
|
+
return result
|
pyoaev/apis/tag.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from typing import Any, Dict
|
|
2
|
+
|
|
3
|
+
from pyoaev import exceptions as exc
|
|
4
|
+
from pyoaev.base import RESTManager, RESTObject
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Tag(RESTObject):
|
|
8
|
+
_id_attr = "tag_id"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class TagManager(RESTManager):
|
|
12
|
+
_path = "/tags"
|
|
13
|
+
_obj_cls = Tag
|
|
14
|
+
|
|
15
|
+
@exc.on_http_error(exc.OpenAEVUpdateError)
|
|
16
|
+
def upsert(self, data: Dict[str, Any], **kwargs: Any) -> Dict[str, Any]:
|
|
17
|
+
path = f"{self.path}/upsert"
|
|
18
|
+
result = self.openaev.http_post(path, post_data=data, **kwargs)
|
|
19
|
+
return result
|
pyoaev/apis/team.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from typing import Any, Dict
|
|
2
|
+
|
|
3
|
+
from pyoaev import exceptions as exc
|
|
4
|
+
from pyoaev.base import RESTManager, RESTObject
|
|
5
|
+
from pyoaev.mixins import CreateMixin, ListMixin, UpdateMixin
|
|
6
|
+
from pyoaev.utils import RequiredOptional
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Team(RESTObject):
|
|
10
|
+
_id_attr = "team_id"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class TeamManager(CreateMixin, ListMixin, UpdateMixin, RESTManager):
|
|
14
|
+
_path = "/teams"
|
|
15
|
+
_obj_cls = Team
|
|
16
|
+
_create_attrs = RequiredOptional(
|
|
17
|
+
required=("team_name",),
|
|
18
|
+
optional=("team_description", "team_organization", "team_tags"),
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
@exc.on_http_error(exc.OpenAEVUpdateError)
|
|
22
|
+
def upsert(self, team: Dict[str, Any], **kwargs: Any) -> Dict[str, Any]:
|
|
23
|
+
path = f"{self.path}/upsert"
|
|
24
|
+
result = self.openaev.http_post(path, post_data=team, **kwargs)
|
|
25
|
+
return result
|
pyoaev/apis/user.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from typing import Any, Dict
|
|
2
|
+
|
|
3
|
+
from pyoaev import exceptions as exc
|
|
4
|
+
from pyoaev.base import RESTManager, RESTObject
|
|
5
|
+
from pyoaev.mixins import CreateMixin, ListMixin, UpdateMixin
|
|
6
|
+
from pyoaev.utils import RequiredOptional
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class User(RESTObject):
|
|
10
|
+
_id_attr = "user_id"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class UserManager(CreateMixin, ListMixin, UpdateMixin, RESTManager):
|
|
14
|
+
_path = "/players"
|
|
15
|
+
_obj_cls = User
|
|
16
|
+
_create_attrs = RequiredOptional(
|
|
17
|
+
required=("user_email",),
|
|
18
|
+
optional=(
|
|
19
|
+
"user_firstname",
|
|
20
|
+
"user_lastname",
|
|
21
|
+
"user_organization",
|
|
22
|
+
"user_country",
|
|
23
|
+
"user_tags",
|
|
24
|
+
),
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
@exc.on_http_error(exc.OpenAEVUpdateError)
|
|
28
|
+
def upsert(self, user: Dict[str, Any], **kwargs: Any) -> Dict[str, Any]:
|
|
29
|
+
path = f"{self.path}/upsert"
|
|
30
|
+
result = self.openaev.http_post(path, post_data=user, **kwargs)
|
|
31
|
+
return result
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Defines http backends for processing http requests
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from .backend import RequestsBackend, RequestsResponse, TokenAuth
|
|
6
|
+
|
|
7
|
+
DefaultBackend = RequestsBackend
|
|
8
|
+
DefaultResponse = RequestsResponse
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"DefaultBackend",
|
|
12
|
+
"DefaultResponse",
|
|
13
|
+
"TokenAuth",
|
|
14
|
+
]
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import dataclasses
|
|
2
|
+
import json
|
|
3
|
+
from typing import TYPE_CHECKING, Any, BinaryIO, Dict, Optional, Union
|
|
4
|
+
|
|
5
|
+
import requests
|
|
6
|
+
from requests import PreparedRequest
|
|
7
|
+
from requests.auth import AuthBase
|
|
8
|
+
from requests.structures import CaseInsensitiveDict
|
|
9
|
+
from requests_toolbelt.multipart.encoder import MultipartEncoder # type: ignore
|
|
10
|
+
|
|
11
|
+
from pyoaev.backends import protocol
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Auth:
|
|
15
|
+
def __init__(self, token: str):
|
|
16
|
+
self.token = token
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class TokenAuth(Auth, AuthBase):
|
|
20
|
+
def __call__(self, r: PreparedRequest) -> PreparedRequest:
|
|
21
|
+
r.headers["Authorization"] = f"Bearer {self.token}"
|
|
22
|
+
return r
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclasses.dataclass
|
|
26
|
+
class SendData:
|
|
27
|
+
content_type: str
|
|
28
|
+
data: Optional[Union[Dict[str, Any], MultipartEncoder]] = None
|
|
29
|
+
json: Optional[Union[Dict[str, Any], bytes]] = None
|
|
30
|
+
|
|
31
|
+
def __post_init__(self) -> None:
|
|
32
|
+
if self.json is not None and self.data is not None:
|
|
33
|
+
raise ValueError(
|
|
34
|
+
f"`json` and `data` are mutually exclusive. Only one can be set. "
|
|
35
|
+
f"json={self.json!r} data={self.data!r}"
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class RequestsResponse(protocol.BackendResponse):
|
|
40
|
+
def __init__(self, response: requests.Response) -> None:
|
|
41
|
+
self._response: requests.Response = response
|
|
42
|
+
|
|
43
|
+
@property
|
|
44
|
+
def response(self) -> requests.Response:
|
|
45
|
+
return self._response
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def status_code(self) -> int:
|
|
49
|
+
return self._response.status_code
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def headers(self) -> CaseInsensitiveDict[str]:
|
|
53
|
+
return self._response.headers
|
|
54
|
+
|
|
55
|
+
@property
|
|
56
|
+
def content(self) -> bytes:
|
|
57
|
+
return self._response.content
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def reason(self) -> str:
|
|
61
|
+
return self._response.reason
|
|
62
|
+
|
|
63
|
+
def json(self) -> Any:
|
|
64
|
+
return self._response.json()
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class RequestsBackend(protocol.Backend):
|
|
68
|
+
def __init__(self, session: Optional[requests.Session] = None) -> None:
|
|
69
|
+
self._client: requests.Session = session or requests.Session()
|
|
70
|
+
|
|
71
|
+
@property
|
|
72
|
+
def client(self) -> requests.Session:
|
|
73
|
+
return self._client
|
|
74
|
+
|
|
75
|
+
@staticmethod
|
|
76
|
+
def prepare_send_data(
|
|
77
|
+
files: Optional[Dict[str, Any]] = None,
|
|
78
|
+
post_data: Optional[Union[Dict[str, Any], bytes, BinaryIO]] = None,
|
|
79
|
+
raw: bool = False,
|
|
80
|
+
) -> SendData:
|
|
81
|
+
if files is not None:
|
|
82
|
+
json_data = {"input": (None, json.dumps(post_data), "application/json")}
|
|
83
|
+
post_data = {**files, **json_data}
|
|
84
|
+
multipart_encoder = MultipartEncoder(fields=post_data)
|
|
85
|
+
return SendData(
|
|
86
|
+
data=multipart_encoder, content_type=multipart_encoder.content_type
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
if raw and post_data:
|
|
90
|
+
return SendData(data=post_data, content_type="application/octet-stream")
|
|
91
|
+
|
|
92
|
+
if TYPE_CHECKING:
|
|
93
|
+
assert not isinstance(post_data, BinaryIO)
|
|
94
|
+
|
|
95
|
+
return SendData(json=post_data, content_type="application/json")
|
|
96
|
+
|
|
97
|
+
def http_request(
|
|
98
|
+
self,
|
|
99
|
+
method: str,
|
|
100
|
+
url: str,
|
|
101
|
+
json: Optional[Union[Dict[str, Any], bytes]] = None,
|
|
102
|
+
data: Optional[Union[Dict[str, Any], MultipartEncoder]] = None,
|
|
103
|
+
params: Optional[Any] = None,
|
|
104
|
+
timeout: Optional[float] = None,
|
|
105
|
+
verify: Optional[Union[bool, str]] = True,
|
|
106
|
+
stream: Optional[bool] = False,
|
|
107
|
+
**kwargs: Any,
|
|
108
|
+
) -> RequestsResponse:
|
|
109
|
+
"""Make HTTP request
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
method: The HTTP method to call ('get', 'post', 'put', 'delete', etc.)
|
|
113
|
+
url: The full URL
|
|
114
|
+
data: The data to send to the server in the body of the request
|
|
115
|
+
json: Data to send in the body in json by default
|
|
116
|
+
timeout: The timeout, in seconds, for the request
|
|
117
|
+
verify: Whether SSL certificates should be validated. If
|
|
118
|
+
the value is a string, it is the path to a CA file used for
|
|
119
|
+
certificate validation.
|
|
120
|
+
stream: Whether the data should be streamed
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
A requests Response object.
|
|
124
|
+
"""
|
|
125
|
+
response: requests.Response = self._client.request(
|
|
126
|
+
method=method,
|
|
127
|
+
url=url,
|
|
128
|
+
params=params,
|
|
129
|
+
data=data,
|
|
130
|
+
timeout=timeout,
|
|
131
|
+
stream=stream,
|
|
132
|
+
verify=verify,
|
|
133
|
+
json=json,
|
|
134
|
+
**kwargs,
|
|
135
|
+
)
|
|
136
|
+
return RequestsResponse(response=response)
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
import sys
|
|
3
|
+
from typing import Any, Dict, Optional, Union
|
|
4
|
+
|
|
5
|
+
import requests
|
|
6
|
+
from requests_toolbelt.multipart.encoder import MultipartEncoder # type: ignore
|
|
7
|
+
|
|
8
|
+
if sys.version_info >= (3, 8):
|
|
9
|
+
from typing import Protocol
|
|
10
|
+
else:
|
|
11
|
+
from typing_extensions import Protocol
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class BackendResponse(Protocol):
|
|
15
|
+
@abc.abstractmethod
|
|
16
|
+
def __init__(self, response: requests.Response) -> None: ...
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class Backend(Protocol):
|
|
20
|
+
@abc.abstractmethod
|
|
21
|
+
def http_request(
|
|
22
|
+
self,
|
|
23
|
+
method: str,
|
|
24
|
+
url: str,
|
|
25
|
+
json: Optional[Union[Dict[str, Any], bytes]],
|
|
26
|
+
data: Optional[Union[Dict[str, Any], MultipartEncoder]],
|
|
27
|
+
params: Optional[Any],
|
|
28
|
+
timeout: Optional[float],
|
|
29
|
+
verify: Optional[Union[bool, str]],
|
|
30
|
+
stream: Optional[bool],
|
|
31
|
+
**kwargs: Any,
|
|
32
|
+
) -> BackendResponse: ...
|