-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge remote-tracking branch 'origin/github_actions' into github_actions
- Loading branch information
Showing
71 changed files
with
5,184 additions
and
0 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,161 @@ | ||
## Быстрый старт | ||
|
||
## HTTPX_Service | ||
|
||
Если не нужно использовать сконфигурированный транспорт, используйте HTTPXService (для async -> AsyncHttpxService) | ||
|
||
```python | ||
from kontur.httptoolkitcore import HttpxService | ||
from kontur.httptoolkitcore import Header | ||
|
||
headers = ( | ||
Header(name="My-Header", value="my-value", is_sensitive=False), | ||
) | ||
httpbin = HttpxService("http://httpbin.org", headers=headers) | ||
httpbin.get("/get") | ||
httpbin.post("/post") | ||
``` | ||
|
||
## Service | ||
|
||
Если нужно использовать сконфигурированный transport, используйте Service. (Service -> HttpxTransport, AsyncService -> AsyncHttpxTransport) | ||
|
||
```python | ||
### Sync | ||
|
||
from kontur.httptoolkitcore import Service, Header | ||
from kontur.httptoolkitcore.transport import HttpxTransport | ||
|
||
|
||
class DummyService(Service): | ||
pass | ||
|
||
|
||
DummyService( | ||
headers=(Header(name="ServiceHeader", value="service-header", is_sensitive=False),), | ||
transport=HttpxTransport(base_url="https://example.com:4321", proxies={"http://": "http://10.10.1.10:3128"}), | ||
## base_url в таком случае передается в transport | ||
) | ||
``` | ||
|
||
```python | ||
### Async | ||
|
||
from kontur.httptoolkitcore import AsyncService, Header | ||
from kontur.httptoolkitcore.transport import AsyncHttpxTransport | ||
|
||
|
||
class DummyService(AsyncService): | ||
pass | ||
|
||
|
||
DummyService( | ||
headers=(Header(name="ServiceHeader", value="service-header", is_sensitive=False),), | ||
transport=AsyncHttpxTransport(base_url="https://example.com:4321", proxies={"http://": "http://10.10.1.10:3128"}), | ||
## base_url в таком случае передается в transport | ||
) | ||
``` | ||
|
||
### Отправка запроса | ||
|
||
```python | ||
### Async | ||
|
||
from kontur.httptoolkitcore import Service, Header, HttpMethod | ||
from kontur.httptoolkitcore.transport import HttpxTransport | ||
from kontur.httptoolkitcore.request import Request | ||
|
||
|
||
class DummyService(Service): | ||
pass | ||
|
||
|
||
service = DummyService( | ||
headers=(Header(name="ServiceHeader", value="service-header", is_sensitive=False),), | ||
transport=HttpxTransport(base_url="https://example.com:4321", proxies={"http://": "http://10.10.1.10:3128"}), | ||
## base_url в таком случае передается в transport | ||
) | ||
|
||
# По методу | ||
service.post( | ||
path="/somewhere", | ||
headers=(Header(name="SuperSecret", value="big_secret", is_sensitive=True, create_mask=lambda value: value[-4:])), | ||
params={"over": "the rainbow"}, | ||
body="Something", | ||
) | ||
|
||
# По request | ||
service.request(Request(method=HttpMethod.POST, body="Request", params={}, path="")) | ||
``` | ||
|
||
### Отправка особых типов | ||
|
||
```python | ||
from kontur.httptoolkitcore import Service, Header, HttpMethod | ||
from kontur.httptoolkitcore.transport import HttpxTransport | ||
from kontur.httptoolkitcore.request import Request | ||
|
||
|
||
class DummyService(Service): | ||
pass | ||
|
||
|
||
service = DummyService( | ||
headers=(Header(name="ServiceHeader", value="service-header", is_sensitive=False),), | ||
transport=HttpxTransport(base_url="https://example.com:4321", proxies={"http://": "http://10.10.1.10:3128"}), | ||
## base_url в таком случае передается в transport | ||
) | ||
|
||
# Отправить JSON (json_encoder задан по-умолчанию, но можно его поменять в transport) | ||
# Не отправлять вместе с body и с files | ||
service.post( | ||
path="/somewhere", | ||
headers=(Header(name="SuperSecret", value="big_secret", is_sensitive=True, create_mask=lambda value: value[-4:])), | ||
params={"over": "the rainbow"}, | ||
json={ | ||
"param1": 1, | ||
"param2": 2, | ||
}, | ||
) | ||
|
||
# Отправить multipart-files в формате Dict[str, Union[BinaryIO, Tuple[str, BinaryIO, str]]] | ||
# Можно отправлять вместе с body, но нельзя с json | ||
service.post( | ||
path="/somewhere", | ||
headers=(Header(name="SuperSecret", value="big_secret", is_sensitive=True, create_mask=lambda value: value[-4:]),), | ||
params={"over": "the rainbow"}, | ||
files={"upload-file": open("report.xls", "rb")}, | ||
# другой формат files = {'upload-file': ('report.xls', open('report.xls', 'rb'), 'application/vnd.ms-excel')} | ||
) | ||
``` | ||
|
||
## Имя логгера библиотеки | ||
|
||
kontur.httptoolkitcore | ||
|
||
## Уровень логирования по-умолчанию | ||
|
||
logging.INFO | ||
|
||
## Пример настройки логирования | ||
|
||
```python | ||
import logging | ||
import kontur.httptoolkitcore | ||
|
||
logging.basicConfig(level="INFO") | ||
|
||
|
||
class MyService(kontur.httptoolkit.HttpxService): | ||
def test(self): | ||
self.get("/") | ||
|
||
|
||
service = MyService("https://kontur.ru") | ||
|
||
service.test() | ||
``` | ||
## Вывод | ||
```python | ||
INFO:kontur.httptoolkit.transport._sync:Sending GET https://kontur.ru/ | ||
``` |
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,27 @@ | ||
import logging | ||
|
||
from kontur.httptoolkitcore.errors import ServiceError, suppress_http_error | ||
from kontur.httptoolkitcore.header import AuthSidHeader, BasicAuthHeader, BearerAuthHeader, Header | ||
from kontur.httptoolkitcore.service import AsyncService, Service | ||
from kontur.httptoolkitcore.httpx_service import HttpxService, AsyncHttpxService | ||
from kontur.httptoolkitcore.http_method import HttpMethod | ||
|
||
__all__ = [ | ||
"Service", | ||
"AsyncService", | ||
"HttpxService", | ||
"AsyncHttpxService", | ||
"ServiceError", | ||
"suppress_http_error", | ||
"Header", | ||
"AuthSidHeader", | ||
"BasicAuthHeader", | ||
"BearerAuthHeader", | ||
"HttpMethod", | ||
] | ||
|
||
logger = logging.getLogger("kontur.httptoolkitcore") | ||
logger.addHandler(logging.NullHandler()) | ||
logger.setLevel(logging.INFO) | ||
httpx_log = logging.getLogger("httpx") | ||
httpx_log.propagate = False |
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,3 @@ | ||
from os import environ | ||
|
||
__version__ = environ.get("CI_COMMIT_TAG") or "0.dev0" |
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,14 @@ | ||
import json | ||
import uuid | ||
from typing import Any | ||
|
||
|
||
class DefaultJSONEncoder(json.JSONEncoder): | ||
def default(self, obj: Any) -> str: | ||
if isinstance(obj, uuid.UUID): | ||
return str(obj) | ||
return super().default(obj) | ||
|
||
|
||
def default_json_encoder(content: Any) -> str: | ||
return json.dumps(content, cls=DefaultJSONEncoder) |
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,116 @@ | ||
from contextlib import contextmanager | ||
from typing import Optional | ||
|
||
|
||
class Error(Exception): | ||
def __str__(self): | ||
context = self.__cause__ or self.__context__ | ||
|
||
if context: | ||
return "{}: {}".format(type(context).__name__, context) | ||
else: | ||
return super().__str__() | ||
|
||
|
||
class TransportError(Error): | ||
def __init__(self, request) -> None: | ||
self.request = request | ||
|
||
|
||
class ServiceError(Error): | ||
MESSAGE_DELIMITER = "\n" | ||
|
||
def __init__(self, request, response=None): | ||
self._request = request | ||
self._response = response | ||
|
||
def __str__(self): | ||
description = self._description() | ||
context = self.__cause__ or self.__context__ | ||
|
||
if context: | ||
description = self._concatenate(str(context), description) | ||
|
||
return description | ||
|
||
@property | ||
def response(self): | ||
return self._response | ||
|
||
def response_code(self) -> Optional[int]: | ||
if self._response is not None: | ||
return self._response.status_code | ||
return None | ||
|
||
def response_body(self) -> Optional[str]: | ||
if self._response is not None: | ||
return self._response.text | ||
return None | ||
|
||
def _description(self): | ||
return self._concatenate(self._request_description(), self._response_description()) | ||
|
||
def _request_description(self): | ||
return self._concatenate( | ||
"Request: {} {}".format(self._request.method.upper(), self._request.url), | ||
"Request headers: {}".format(self._request.filtered_headers), | ||
"Proxies: {}".format(self._request.proxies), | ||
) | ||
|
||
def _response_description(self): | ||
if self._response is not None: | ||
return self._concatenate( | ||
"Response: {} {}".format(self.response_code(), self._response.reason), | ||
"Response headers: {}".format(self._response.headers), | ||
"Response body: {}".format(self.response_body()), | ||
) | ||
|
||
def __getstate__(self): | ||
contexts = [self.__cause__ or self.__context__] | ||
while contexts[len(contexts) - 1]: | ||
last_context = contexts[len(contexts) - 1] | ||
contexts.append(last_context.__context__ or last_context.__cause__) | ||
return { | ||
"contexts": contexts, | ||
} | ||
|
||
def __setstate__(self, state): | ||
contexts = state["contexts"] | ||
self.__context__ = contexts[0] | ||
context = self.__context__ | ||
for i in range(0, len(contexts) - 1): | ||
context.__context__ = contexts[i] | ||
|
||
def __reduce__(self): | ||
return (self.__class__, (self._request, self._response), self.__getstate__()) | ||
|
||
def _concatenate(self, *strings): | ||
return self.MESSAGE_DELIMITER.join(filter(None, strings)) | ||
|
||
|
||
class HttpError(ServiceError): | ||
def __init__(self, request, response): | ||
super().__init__(request, response) | ||
|
||
|
||
class HttpErrorTypecast: | ||
HTTP_BAD_REQUEST_CODE = 400 | ||
|
||
@classmethod | ||
def is_bad_request_error(cls, http_error): | ||
return isinstance(http_error, Error) and http_error.response_code() == cls.HTTP_BAD_REQUEST_CODE | ||
|
||
|
||
@contextmanager | ||
def suppress_http_error(*statuses): | ||
""" | ||
Suppress http error with specified status codes. | ||
:param statuses: list of status codes | ||
""" | ||
try: | ||
yield | ||
except HttpError as e: | ||
if e.response.status_code in statuses: | ||
return None | ||
raise |
Oops, something went wrong.