-
Notifications
You must be signed in to change notification settings - Fork 67
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #49 from markowanga/fix/add_requests_interceptor
Add interceptor interface into WebClient
- Loading branch information
Showing
10 changed files
with
225 additions
and
10 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 |
---|---|---|
@@ -1,3 +1,4 @@ | ||
requests | ||
pandas | ||
arrow | ||
retrying |
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 |
---|---|---|
|
@@ -8,7 +8,7 @@ | |
|
||
setuptools.setup( | ||
name="stweet", | ||
version="1.2.2", | ||
version="1.3.0", | ||
author="Marcin Wątroba", | ||
author_email="[email protected]", | ||
description="Package to scrap tweets", | ||
|
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
Empty file.
55 changes: 55 additions & 0 deletions
55
stweet/http_request/interceptor/logging_requests_web_client_interceptor.py
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,55 @@ | ||
"""Class of LoggingRequestsWebClientInterceptor.""" | ||
import logging | ||
from http.client import HTTPConnection | ||
from typing import List | ||
|
||
from .. import RequestsWebClient, WebClient, RequestDetails, RequestResponse | ||
|
||
|
||
class LoggingRequestsWebClientInterceptor(WebClient.WebClientInterceptor): | ||
"""Class of LoggingRequestsWebClientInterceptor.""" | ||
|
||
@staticmethod | ||
def _debug_requests_on(): | ||
"""Switches on logging of the requests module.""" | ||
HTTPConnection.debuglevel = 1 | ||
|
||
logging.basicConfig() | ||
logging.getLogger().setLevel(logging.DEBUG) | ||
requests_log = logging.getLogger("requests.packages.urllib3") | ||
requests_log.setLevel(logging.DEBUG) | ||
requests_log.propagate = True | ||
|
||
@staticmethod | ||
def _debug_requests_off(): | ||
"""Switches off logging of the requests module, might be some side-effects.""" | ||
HTTPConnection.debuglevel = 0 | ||
|
||
root_logger = logging.getLogger() | ||
root_logger.setLevel(logging.WARNING) | ||
root_logger.handlers = [] | ||
requests_log = logging.getLogger("requests.packages.urllib3") | ||
requests_log.setLevel(logging.NOTSET) | ||
requests_log.propagate = False | ||
|
||
def logs_to_show(self, params: RequestDetails) -> bool: | ||
"""Method to decide that show logs of request. | ||
Method can be overridden and then the logs will be filtered – example by request url. | ||
""" | ||
return True | ||
|
||
def intercept( | ||
self, | ||
requests_details: RequestDetails, | ||
next_interceptors: List[WebClient.WebClientInterceptor], | ||
web_client: RequestsWebClient | ||
) -> RequestResponse: | ||
"""Method show logs when predicate is true. Uses static field so it can be problem with concurrency.""" | ||
is_to_log = self.logs_to_show(requests_details) | ||
if is_to_log: | ||
LoggingRequestsWebClientInterceptor._debug_requests_on() | ||
to_return = self.get_response(requests_details, next_interceptors, web_client) | ||
if is_to_log: | ||
LoggingRequestsWebClientInterceptor._debug_requests_off() | ||
return to_return |
50 changes: 50 additions & 0 deletions
50
stweet/http_request/interceptor/params_response_log_web_client_interceptor.py
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,50 @@ | ||
"""Class of ParamsResponseLogWebClientInterceptor.""" | ||
import threading | ||
from typing import List | ||
|
||
from .. import RequestsWebClient, WebClient, RequestDetails, RequestResponse | ||
|
||
|
||
class ParamsResponseLogWebClientInterceptor(WebClient.WebClientInterceptor): | ||
"""Class of ParamsResponseLogWebClientInterceptor. | ||
Interceptor log input params and out response. | ||
""" | ||
|
||
_counter: int | ||
_lock: threading.Lock | ||
|
||
def __init__(self): | ||
"""Constructor of ParamsResponseLogWebClientInterceptor.""" | ||
self._value = 0 | ||
self._lock = threading.Lock() | ||
|
||
def increment(self) -> int: | ||
"""Thread safe increment. Returns old value.""" | ||
with self._lock: | ||
to_return = self._value | ||
self._value += 1 | ||
return to_return | ||
|
||
def logs_to_show(self, params: RequestDetails) -> bool: | ||
"""Method to decide that show logs of request. | ||
Method can be overridden and then the logs will be filtered – example by request url. | ||
""" | ||
return True | ||
|
||
def intercept( | ||
self, | ||
requests_details: RequestDetails, | ||
next_interceptors: List[WebClient.WebClientInterceptor], | ||
web_client: RequestsWebClient | ||
) -> RequestResponse: | ||
"""Method show logs when predicate is true. Uses static field so it can be problem with concurrency.""" | ||
is_to_log = self.logs_to_show(requests_details) | ||
index = self.increment() | ||
if is_to_log: | ||
print(f'{index} -- {requests_details}') | ||
to_return = self.get_response(requests_details, next_interceptors, web_client) | ||
if is_to_log: | ||
print(f'{index} -- {to_return}') | ||
return to_return |
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 |
---|---|---|
@@ -1,13 +1,60 @@ | ||
"""Web client abstract class.""" | ||
from abc import abstractmethod | ||
from __future__ import annotations | ||
|
||
from abc import abstractmethod, ABC | ||
from typing import List, Optional | ||
|
||
from .request_details import RequestDetails | ||
from .request_response import RequestResponse | ||
|
||
|
||
def _run_request_with_interceptors( | ||
requests_details: RequestDetails, | ||
next_interceptors: List[WebClient.WebClientInterceptor], | ||
web_client: WebClient | ||
) -> RequestResponse: | ||
return next_interceptors[0].intercept(requests_details, next_interceptors[1:], web_client) if len( | ||
next_interceptors) > 0 else web_client.run_clear_request(requests_details) | ||
|
||
|
||
class WebClient: | ||
"""Web client abstract class.""" | ||
|
||
_interceptors: List[WebClientInterceptor] | ||
|
||
def __init__(self, interceptors: Optional[List[WebClientInterceptor]]): | ||
"""Base constructor of class.""" | ||
self._interceptors = [] if interceptors is None else interceptors | ||
|
||
def run_request(self, requests_details: RequestDetails) -> RequestResponse: | ||
"""Method process the request. Method wrap request with interceptors.""" | ||
return _run_request_with_interceptors(requests_details, self._interceptors, self) | ||
|
||
@abstractmethod | ||
def run_request(self, params: RequestDetails) -> RequestResponse: | ||
"""Abstract method to run request.""" | ||
def run_clear_request(self, params: RequestDetails) -> RequestResponse: | ||
"""Abstract method to run only the request.""" | ||
|
||
class WebClientInterceptor(ABC): | ||
"""Abstract class of web client interceptor.""" | ||
|
||
@staticmethod | ||
def get_response( | ||
requests_details: RequestDetails, | ||
next_interceptors: List[WebClient.WebClientInterceptor], | ||
web_client: WebClient | ||
) -> RequestResponse: | ||
"""Method process request. If any interceptor passes method wrap request with this.""" | ||
return _run_request_with_interceptors(requests_details, next_interceptors, web_client) | ||
|
||
@abstractmethod | ||
def intercept( | ||
self, | ||
requests_details: RequestDetails, | ||
next_interceptors: List[WebClient.WebClientInterceptor], | ||
web_client: WebClient | ||
) -> RequestResponse: | ||
"""Interceptor method of request. | ||
Method need to call WebClientInterceptor.get_response to process request by next interceptors | ||
and client. | ||
""" |
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,50 @@ | ||
import sys | ||
from io import StringIO | ||
|
||
import stweet as st | ||
from stweet.auth import SimpleAuthTokenProvider | ||
from stweet.http_request import HttpMethod | ||
from stweet.http_request.interceptor.logging_requests_web_client_interceptor import LoggingRequestsWebClientInterceptor | ||
from stweet.http_request.interceptor.params_response_log_web_client_interceptor import \ | ||
ParamsResponseLogWebClientInterceptor | ||
|
||
|
||
def get_example_request_details() -> st.http_request.RequestDetails: | ||
return st.http_request.RequestDetails( | ||
http_method=HttpMethod.GET, | ||
url='https://api.github.com/events', | ||
params=dict({}), | ||
headers=dict({}), | ||
timeout=200 | ||
) | ||
|
||
|
||
def start_redirect_output() -> StringIO: | ||
captured_output = StringIO() | ||
sys.stdout = captured_output | ||
sys.stderr = captured_output | ||
return captured_output | ||
|
||
|
||
def stop_redirect_output(): | ||
sys.stdout = sys.__stdout__ | ||
sys.stderr = sys.__stderr__ | ||
|
||
|
||
def test_logging_requests_web_client_interceptor(): | ||
captured_output = start_redirect_output() | ||
st.RequestsWebClient(interceptors=[LoggingRequestsWebClientInterceptor()]).run_request( | ||
SimpleAuthTokenProvider._get_auth_request_details()) | ||
stop_redirect_output() | ||
content = captured_output.getvalue() | ||
assert "send: b'POST /1.1/guest/activate.json HTTP/1.1" in content | ||
|
||
|
||
def test_params_response_log_web_client_interceptor(): | ||
captured_output = start_redirect_output() | ||
st.RequestsWebClient(interceptors=[ParamsResponseLogWebClientInterceptor()]).run_request( | ||
SimpleAuthTokenProvider._get_auth_request_details()) | ||
stop_redirect_output() | ||
content = captured_output.getvalue() | ||
assert "RequestDetails(" in content | ||
assert "RequestResponse(" in content |