From c660809302a9c42c4e1562ca8feec1f7c7df1f3f Mon Sep 17 00:00:00 2001 From: jbirddog <100367399+jbirddog@users.noreply.github.com> Date: Mon, 1 May 2023 10:34:38 -0400 Subject: [PATCH] Next version of http get/post commands (#5) --- .gitignore | 2 + poetry.lock | 108 +++++++++---------- pyproject.toml | 4 +- src/connector_http/commands/getRequestV2.py | 91 ++++++++++++++++ src/connector_http/commands/postRequestV2.py | 68 ++++++++++++ 5 files changed, 217 insertions(+), 56 deletions(-) create mode 100644 src/connector_http/commands/getRequestV2.py create mode 100644 src/connector_http/commands/postRequestV2.py diff --git a/.gitignore b/.gitignore index b6e4761..91939da 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +*~ + # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] diff --git a/poetry.lock b/poetry.lock index c2e6bc2..292a134 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,3 +1,5 @@ +# This file is automatically @generated by Poetry 1.4.2 and should not be changed by hand. + [[package]] name = "certifi" version = "2022.12.7" @@ -5,6 +7,10 @@ description = "Python package for providing Mozilla's CA Bundle." category = "main" optional = false python-versions = ">=3.6" +files = [ + {file = "certifi-2022.12.7-py3-none-any.whl", hash = "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18"}, + {file = "certifi-2022.12.7.tar.gz", hash = "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3"}, +] [[package]] name = "charset-normalizer" @@ -13,57 +19,7 @@ description = "The Real First Universal Charset Detector. Open, modern and activ category = "main" optional = false python-versions = "*" - -[[package]] -name = "idna" -version = "3.4" -description = "Internationalized Domain Names in Applications (IDNA)" -category = "main" -optional = false -python-versions = ">=3.5" - -[[package]] -name = "requests" -version = "2.28.2" -description = "Python HTTP for Humans." -category = "main" -optional = false -python-versions = ">=3.7, <4" - -[package.dependencies] -certifi = ">=2017.4.17" -charset-normalizer = ">=2,<4" -idna = ">=2.5,<4" -urllib3 = ">=1.21.1,<1.27" - -[package.extras] -socks = ["PySocks (>=1.5.6,!=1.5.7)"] -use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] - -[[package]] -name = "urllib3" -version = "1.26.14" -description = "HTTP library with thread-safe connection pooling, file post, and more." -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" - -[package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] -secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] -socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] - -[metadata] -lock-version = "1.1" -python-versions = "^3.9" -content-hash = "57023848b3e1e5f7f7970725bbc3c1669558579faaa10b433df88d1623277dc1" - -[metadata.files] -certifi = [ - {file = "certifi-2022.12.7-py3-none-any.whl", hash = "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18"}, - {file = "certifi-2022.12.7.tar.gz", hash = "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3"}, -] -charset-normalizer = [ +files = [ {file = "charset-normalizer-3.0.1.tar.gz", hash = "sha256:ebea339af930f8ca5d7a699b921106c6e29c617fe9606fa7baa043c1cdae326f"}, {file = "charset_normalizer-3.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:88600c72ef7587fe1708fd242b385b6ed4b8904976d5da0893e31df8b3480cb6"}, {file = "charset_normalizer-3.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c75ffc45f25324e68ab238cb4b5c0a38cd1c3d7f1fb1f72b5541de469e2247db"}, @@ -153,15 +109,59 @@ charset-normalizer = [ {file = "charset_normalizer-3.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:0a11e971ed097d24c534c037d298ad32c6ce81a45736d31e0ff0ad37ab437d59"}, {file = "charset_normalizer-3.0.1-py3-none-any.whl", hash = "sha256:7e189e2e1d3ed2f4aebabd2d5b0f931e883676e51c7624826e0a4e5fe8a0bf24"}, ] -idna = [ + +[[package]] +name = "idna" +version = "3.4" +description = "Internationalized Domain Names in Applications (IDNA)" +category = "main" +optional = false +python-versions = ">=3.5" +files = [ {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, ] -requests = [ + +[[package]] +name = "requests" +version = "2.28.2" +description = "Python HTTP for Humans." +category = "main" +optional = false +python-versions = ">=3.7, <4" +files = [ {file = "requests-2.28.2-py3-none-any.whl", hash = "sha256:64299f4909223da747622c030b781c0d7811e359c37124b4bd368fb8c6518baa"}, {file = "requests-2.28.2.tar.gz", hash = "sha256:98b1b2782e3c6c4904938b84c0eb932721069dfdb9134313beff7c83c2df24bf"}, ] -urllib3 = [ + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<1.27" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "urllib3" +version = "1.26.14" +description = "HTTP library with thread-safe connection pooling, file post, and more." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +files = [ {file = "urllib3-1.26.14-py2.py3-none-any.whl", hash = "sha256:75edcdc2f7d85b137124a6c3c9fc3933cdeaa12ecb9a6a959f22797a0feca7e1"}, {file = "urllib3-1.26.14.tar.gz", hash = "sha256:076907bf8fd355cde77728471316625a4d2f7e713c125f51953bb5b3eecf4f72"}, ] + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] +secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] +socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] + +[metadata] +lock-version = "2.0" +python-versions = "^3.9" +content-hash = "514b82d94085731af14ad7375f29852ffcad834018325704042f9f0fefe3aea4" diff --git a/pyproject.toml b/pyproject.toml index e2c24c2..2ee25f2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "connector-http" -version = "0.1.0" +version = "0.2.0" description = "Make HTTP Requests available to SpiffWorkflow Service Tasks" authors = ["Jon Herron "] readme = "README.md" @@ -8,7 +8,7 @@ packages = [{include = "connector_http", from = "src" }] [tool.poetry.dependencies] python = "^3.9" -requests = "^2.28.1" +requests = "^2.28.2" [build-system] diff --git a/src/connector_http/commands/getRequestV2.py b/src/connector_http/commands/getRequestV2.py new file mode 100644 index 0000000..6ce6971 --- /dev/null +++ b/src/connector_http/commands/getRequestV2.py @@ -0,0 +1,91 @@ +import json +import time + +import requests + +from typing import Dict +from typing import Optional +from typing import Tuple + +class GetRequestV2: + def __init__(self, + url: str, + headers: Optional[Dict[str, str]] = None, + params: Optional[Dict[str, str]] = None, + basic_auth_username: Optional[str] = None, + basic_auth_password: Optional[str] = None, + attempts: Optional[int] = None, + ): + self.url = url + self.headers = headers or {} + self.params = params or {} + self.basic_auth_username = basic_auth_username + self.basic_auth_password = basic_auth_password + + if not isinstance(attempts, int) or attempts < 1 or attempts > 10: + attempts = 1 + + self.attempts = attempts + + def execute(self, config, task_data): + logs = [] + + def log(msg): + logs.append(f"[{time.time()}] {msg}") + + log(f"Will execute") + + auth = None + if self.basic_auth_username is not None and self.basic_auth_password is not None: + auth = (self.basic_auth_username, self.basic_auth_password) + log("Set auth") + + attempt = 1 + + while attempt <= self.attempts: + response = {} + status = 0 + mimetype = "application/json" + + if attempt > 1: + log("Sleeping before next attempt") + time.sleep(1) + + log(f"Will attempt {attempt} of {self.attempts}") + api_response = None + + try: + log(f"Will call {self.url}") + api_response = requests.get(self.url, self.params, headers=self.headers, auth=auth) + log(f"Did call {self.url}") + + log(f"Will parse response") + status = api_response.status_code + response = json.loads(api_response.text) + log(f"Did parse response") + except Exception as e: + log(f"Did catch exception: {e}") + if len(response) == 0: + response = f'{"error": {e}, "raw_response": {api_response.text}}', + if status == 0: + status = 500 + finally: + log(f"Did attempt {attempt} of {self.attempts}") + + if status // 100 != 5: + break + + attempt += 1 + + log("Did execute") + + result = { + "response": { + "api_response": response, + "spiff__logs": logs, + }, + "status": status, + "mimetype": mimetype, + } + + return result diff --git a/src/connector_http/commands/postRequestV2.py b/src/connector_http/commands/postRequestV2.py new file mode 100644 index 0000000..23a2a4c --- /dev/null +++ b/src/connector_http/commands/postRequestV2.py @@ -0,0 +1,68 @@ +import json +import requests +import time + +from typing import Any +from typing import Dict +from typing import Optional +from typing import Tuple + +class PostRequestV2: + def __init__(self, + url: str, + headers: Optional[Dict[str, str]], + basic_auth_username: Optional[str], + basic_auth_password: Optional[str], + data: Optional[Dict[str, Any]], + ): + self.url = url + self.headers = headers or {} + self.basic_auth_username = basic_auth_username + self.basic_auth_password = basic_auth_password + self.data = data + + def execute(self, config, task_data): + logs = [] + + def log(msg): + logs.append(f"[{time.time()}] {msg}") + + response = {} + status = 0 + mimetype = "application/json" + + log(f"Will execute") + + auth = None + if self.basic_auth_username is not None and self.basic_auth_password is not None: + auth = (self.basic_auth_username, self.basic_auth_password) + log("Set auth") + + try: + log(f"Will call {self.url}") + api_response = requests.post(self.url, headers=self.headers, auth=auth, json=self.data) + log(f"Did call {self.url}") + + log(f"Will parse response") + status = api_response.status_code + response = json.loads(api_response.text) + log(f"Did parse response") + except Exception as e: + log(f"Did catch exception: {e}") + if len(response) == 0: + response = f'{"error": {e}, "raw_response": {api_response.text}}', + if status == 0: + status = 500 + finally: + log("Did execute") + + result = { + "response": { + "api_response": response, + "spiff__logs": logs, + }, + "status": status, + "mimetype": mimetype, + } + + return result