From 6a3af93d16fa603fa6c426b7e90ca592df9caac2 Mon Sep 17 00:00:00 2001 From: sharinetmc Date: Tue, 19 Nov 2024 14:33:42 -1000 Subject: [PATCH 01/10] initial refractor --- parsons/newmode/newmode.py | 1090 +++++++++++++++++++++++++++--------- 1 file changed, 824 insertions(+), 266 deletions(-) diff --git a/parsons/newmode/newmode.py b/parsons/newmode/newmode.py index 7b342528f5..5fdfa571f6 100644 --- a/parsons/newmode/newmode.py +++ b/parsons/newmode/newmode.py @@ -1,325 +1,883 @@ -from Newmode import Client +from parsons.utilities.api_connector import APIConnector from parsons.utilities import check_env -from parsons.etl import Table import logging +import time logger = logging.getLogger(__name__) +API_URL = "https://engage.newmode.net/api/" -class Newmode: - def __init__(self, api_user=None, api_password=None, api_version=None): - """ - Args: - api_user: str - The Newmode api user. Not required if ``NEWMODE_API_USER`` env variable is - passed. - api_password: str - The Newmode api password. Not required if ``NEWMODE_API_PASSWORD`` env variable is - passed. - api_version: str - The Newmode api version. Defaults to "v1.0" or the value of ``NEWMODE_API_VERSION`` - env variable. - Returns: - Newmode class - """ + +class NewMode(object): + """ + Instantiate Class + `Args`: + api_user: str + The username to use for the API requests. Not required if ``NEWMODE_API_URL`` + env variable set. + api_password: str + The password to use for the API requests. Not required if ``NEWMODE_API_PASSWORD`` + env variable set. + api_version: str + The api version to use. Defaults to v2.1 + Returns: + NewMode Class + """ + + def __init__(self, api_user=None, api_password=None, api_version="v2.1"): + self.base_url = check_env.check("NEWMODE_API_URL", API_URL) self.api_user = check_env.check("NEWMODE_API_USER", api_user) self.api_password = check_env.check("NEWMODE_API_PASSWORD", api_password) + self.api_version = check_env.check("NEWMODE_API_VERSION", api_version) + self.headers = {"Content-Type": "application/json"} + self.url = f"{self.base_url}{self.api_version}/" + self.client = APIConnector( + self.api_url_with_version, + auth=(self.api_user, self.api_password), + headers=self.headers, + ) - if api_version is None: - api_version = "v1.0" + def base_request( + self, + endpoint, + method, + requires_csrf=True, + params={}, + ): - self.api_version = check_env.check("NEWMODE_API_VERSION", api_version) + url = endpoint - self.client = Client(api_user, api_password, api_version) + if requires_csrf: + csrf = self.get_csrf_token() + self.headers["X-CSRF-Token"] = csrf - def convert_to_table(self, data): - # Internal method to create a Parsons table from a data element. - table = None - if type(data) is list: - table = Table(data) - else: - table = Table([data]) + response = None + if method == "GET": + response = self.client.get_request(url=url, params=params) + elif method == "PATCH": + response = self.client.patch_request(url=url, params=params) + return response - return table + def get_csrf_token(self, max_attempts=10): + """ + Retrieve a CSRF token for making API requests + `Args:` + max_attempts: int + The maximum number of attempts to get the CSRF token. + `Returns:` + The CSRF token. + """ + for attempt in range(max_attempts): + try: + response = self.base_request( + endpoint="session/token", method="GET", requires_csrf=False + ) + return response + except Exception as e: + logger.warning( + f"Attempt {attempt} at getting CSRF Token failed. Retrying. Error: {e}" + ) + time.sleep(attempt + 1) + logger.warning(f"Error getting CSRF Token after {max_attempts} attempts") def get_tools(self, params={}): """ - Get existing tools. - Args: - params: - Extra parameters sent to New/Mode library. - Returns: - Tools information as table. + Retrieve all tools + `Args:` + params: dict + Query parameters to include in the request. + `Returns:` + Response object from the API request containing tools data. """ - tools = self.client.getTools(params=params) - if tools: - return self.convert_to_table(tools) - else: - logging.warning("Empty tools returned") - return self.convert_to_table([]) + response = self.base_request(endpoint="tool", method="GET", params=params) + return response def get_tool(self, tool_id, params={}): """ - Get specific tool. - Args: - tool_id: - The id of the tool to return. - params: - Extra parameters sent to New/Mode library. - Returns: - Tool information. - """ - tool = self.client.getTool(tool_id, params=params) - if tool: - return tool - else: - logging.warning("Empty tool returned") - return None + Retrieve a specific tool by ID + `Args:` + tool_id: str + The ID of the tool to retrieve. + params: dict + Query parameters to include in the request. + `Returns:` + Response object from the API request containing the tool data. + """ + response = self.base_request( + endpoint=f"tool/{tool_id}", method="GET", params=params + ) + return response def lookup_targets(self, tool_id, search=None, params={}): """ Lookup targets for a given tool - Args: - tool_id: - The tool to lookup targets. - search: - The search criteria. It could be: - - Empty: If empty, return custom targets associated to the tool. - - Postal code: Return targets matched by postal code. - - Lat/Long: Latitude and Longitude pair separated by '::'. - Ex. 45.451596::-73.59912099999997. It will return targets - matched for those coordinates. - - Search term: For your csv tools, this will return targets - matched by given valid search term. - Returns: - Targets information as table. - """ - targets = self.client.lookupTargets(tool_id, search, params=params) - if targets: - data = [] - for key in targets: - if key != "_links": - data.append(targets[key]) - return self.convert_to_table(data) - else: - logging.warning("Empty targets returned") - return self.convert_to_table([]) + `Args:` + tool_id: str + The ID of the tool to lookup targets for. + search: str + The search criteria (optional). + params: dict + Query parameters to include in the request. + `Returns:` + Response object from the API request containing target data. + """ + endpoint = f"lookup/{tool_id}" + if search: + endpoint += f"/{search}" + response = self.base_request(endpoint=endpoint, method="GET", params=params) + return response def get_action(self, tool_id, params={}): """ - Get the action information for a given tool. - Args: - tool_id: - The id of the tool to return. - params: - Extra parameters sent to New/Mode library. - Returns: - Tool action information. - """ - action = self.client.getAction(tool_id, params=params) - if action: - return action - else: - logging.warning("Empty action returned") - return None + Get action information for a specific tool + `Args:` + tool_id: str + The ID of the tool to get action information for. + params: dict + Query parameters to include in the request. + `Returns:` + Response object from the API request containing action data. + """ + response = self.base_request( + endpoint=f"action/{tool_id}", method="GET", params=params + ) + return response def run_action(self, tool_id, payload, params={}): """ - Run specific action with given payload. - Args: - tool_id: - The id of the tool to run. - payload: - Payload data used to run the action. Structure will depend - on the stuff returned by get_action. - params: - Extra parameters sent to New/Mode library. - Returns: - Action link (if otl) or sid. - """ - action = self.client.runAction(tool_id, payload, params=params) - if action: - if "link" in action: - return action["link"] - else: - return action["sid"] - else: - logging.warning("Error in response") - return None + Run a specific action for a tool + `Args:` + tool_id: str + The ID of the tool to run the action for. + payload: dict + The data to post to run the action. + params: dict + Query parameters to include in the request. + `Returns:` + Response object from the API request containing posted outreach information. + """ + response = self.base_request( + endpoint=f"action/{tool_id}", method="PATCH", payload=payload, params=params + ) + return response def get_target(self, target_id, params={}): """ - Get specific target. - Args: - target_id: - The id of the target to return. - params: - Extra parameters sent to New/Mode library. - Returns: - Target information. - """ - target = self.client.getTarget(target_id, params=params) - if target: - return target - else: - logging.warning("Empty target returned") - return None - - def get_targets(self, params={}): - """ - Get all targets - - Args: - params dict: - Extra paramaters sent to New/Mode library - - Returns: - Target information - """ - - targets = self.client.getTargets(params=params) - - if targets: - return self.convert_to_table(targets) - - else: - logging.warning("No targets returned") - return None + Retrieve a specific target by ID + `Args:` + target_id: str + The ID of the target to retrieve. + params: dict + Query parameters to include in the request. + `Returns:` + Response object from the API request containing target data. + """ + response = self.base_request( + endpoint=f"target/{target_id}", method="GET", params=params + ) + return response def get_campaigns(self, params={}): """ - Get existing campaigns. - Args: - params: - Extra parameters sent to New/Mode library. - Returns: - Campaigns information as table. + Retrieve all campaigns + `Args:` + params: dict + Query parameters to include in the request. + `Returns:` + Response object from the API request containing campaigns data. """ - campaigns = self.client.getCampaigns(params=params) - if campaigns: - return self.convert_to_table(campaigns) - else: - logging.warning("Empty campaigns returned") - return self.convert_to_table([]) + response = self.base_request(endpoint="campaign", method="GET", params=params) + return response def get_campaign(self, campaign_id, params={}): """ - Get specific campaign. - Args: - campaign_id: - The id of the campaign to return. - params: - Extra parameters sent to New/Mode library. - Returns: - Campaign information. - """ - campaign = self.client.getCampaign(campaign_id, params=params) - if campaign: - return campaign - else: - logging.warning("Empty campaign returned") - return None + Retrieve a specific campaign by ID + `Args:` + campaign_id: str + The ID of the campaign to retrieve. + params: dict + Query parameters to include in the request. + `Returns:` + Response object from the API request containing campaign data. + """ + response = self.base_request( + endpoint=f"campaign/{campaign_id}", method="GET", params=params + ) + return response def get_organizations(self, params={}): """ - Get existing organizations. - Args: - params: - Extra parameters sent to New/Mode library. - Returns: - Organizations information as table. - """ - organizations = self.client.getOrganizations(params=params) - if organizations: - return self.convert_to_table(organizations) - else: - logging.warning("Empty organizations returned") - return self.convert_to_table([]) + Retrieve all organizations + `Args:` + params: dict + Query parameters to include in the request. + `Returns:` + Response object from the API request containing organizations data. + """ + response = self.base_request( + endpoint="organization", method="GET", params=params + ) + return response def get_organization(self, organization_id, params={}): """ - Get specific organization. - Args: - organization_id: - The id of the organization to return. - params: - Extra parameters sent to New/Mode library. - Returns: - Organization information. - """ - organization = self.client.getOrganization(organization_id, params=params) - if organization: - return organization - else: - logging.warning("Empty organization returned") - return None + Retrieve a specific organization by ID + `Args:` + organization_id: str + The ID of the organization to retrieve. + params: dict + Query parameters to include in the request. + `Returns:` + Response object from the API request containing organization data. + """ + response = self.base_request( + endpoint=f"organization/{organization_id}", method="GET", params=params + ) + return response def get_services(self, params={}): """ - Get existing services. - Args: - params: - Extra parameters sent to New/Mode library. - Returns: - Services information as table. + Retrieve all services + `Args:` + params: dict + Query parameters to include in the request. + `Returns:` + Response object from the API request containing services data. """ - services = self.client.getServices(params=params) - if services: - return self.convert_to_table(services) - else: - logging.warning("Empty services returned") - return self.convert_to_table([]) + response = self.base_request(endpoint="service", method="GET", params=params) + return response def get_service(self, service_id, params={}): """ - Get specific service. - Args: - service_id: - The id of the service to return. - params: - Extra parameters sent to New/Mode library. - Returns: - Service information. - """ - service = self.client.getService(service_id, params=params) - if service: - return service - else: - logging.warning("Empty service returned") - return None + Retrieve a specific service by ID + `Args:` + service_id: str + The ID of the service to retrieve. + params: dict + Query parameters to include in the request. + `Returns:` + Response object from the API request containing service data. + """ + response = self.base_request( + endpoint=f"service/{service_id}", method="GET", params=params + ) + return response + + def get_targets(self, params={}): + """ + Retrieve all targets + `Args:` + params: dict + Query parameters to include in the request. + `Returns:` + Response object from the API request containing targets data. + """ + response = self.base_request(endpoint="target", method="GET", params=params) + return response def get_outreaches(self, tool_id, params={}): """ - Get existing outreaches for a given tool. - Args: - tool_id: - Tool to return outreaches. - params: - Extra parameters sent to New/Mode library. - Returns: - Outreaches information as table. - """ - outreaches = self.client.getOutreaches(tool_id, params=params) - if outreaches: - return self.convert_to_table(outreaches) - else: - logging.warning("Empty outreaches returned") - return self.convert_to_table([]) + Retrieve all outreaches for a specific tool + `Args:` + tool_id: str + The ID of the tool to get outreaches for. + params: dict + Query parameters to include in the request. + `Returns:` + Response object from the API request containing outreaches data. + """ + params["nid"] = str(tool_id) + response = self.base_request( + endpoint="outreach", method="GET", requires_csrf=False, params=params + ) + return response def get_outreach(self, outreach_id, params={}): """ - Get specific outreach. - Args: - outreach_id: - The id of the outreach to return. - params: - Extra parameters sent to New/Mode library. - Returns: - Outreach information. - """ - outreach = self.client.getOutreach(outreach_id, params=params) - if outreach: - return outreach - else: - logging.warning("Empty outreach returned") - return None + Retrieve a specific outreach by ID + `Args:` + outreach_id: str + The ID of the outreach to retrieve. + params: dict + Query parameters to include in the request. + `Returns:` + Response object from the API request containing outreach data. + """ + response = self.base_request( + endpoint=f"outreach/{outreach_id}", method="GET", params=params + ) + return response + + +# from parsons.utilities.api_connector import APIConnector +# from parsons.utilities import check_env +# from parsons.etl import Table +# import logging + +# logger = logging.getLogger(__name__) + +# API_URL = "https://engage.newmode.net/api/" + +# ########## + + +# class NewMode(object): +# """ +# Instantiate Class +# `Args`: +# api_user: +# The username to use for the API requests. Not required if ``NEWMODE_API_URL`` +# env variable set. +# api_password: +# The password to use for the API requests. Not required if ``NEWMODE_API_PASSWORD`` +# env variable set. +# api_version: +# The api version to use. Defaults to v1.0 +# Returns: +# NewMode Class +# """ + +# def __init__(self, api_user=None, api_password=None, api_version="v2.1"): +# self.base_url = check_env.check("NEWMODE_API_URL", API_URL) +# self.api_user = check_env.check("NEWMODE_API_USER", api_user) +# self.api_password = check_env.check("NEWMODE_API_PASSWORD", api_password) +# self.api_version = check_env.check("NEWMODE_API_VERSION", api_version) +# self.headers = {"Content-Type": "application/json"} +# self.url = f"{self.base_url}{self.api_version}/" +# self.client = APIConnector( +# self.api_url_with_version, +# auth=(self.api_user, self.api_password), +# headers=self.headers, +# ) + +# def base_request( +# self, +# endpoint, +# requires_csrf=True, +# params={}, +# ): + +# url = endpoint + +# if requires_csrf: +# csrf = self.get_csrf_token() +# self.headers["X-CSRF-Token"] = csrf +# return self.client.get_request(url=url, params=params) + +# def get_csrf_token(self, max_attempts=10): +# for attempt in range(max_attempts): +# try: +# response = self.base_request(endpoint="session/token", requires_csrf=False) +# return response +# except Exception as e: +# logger.warning(f"Attempt {attempt} at getting CSRF Token failed. Retrying. Error: {e}") +# logger.warning(f"Error getting CSRF Token after {max_attempts} attempts") + +# def get_tools(self, params={}): +# response = self.baseRequest(endpoint="tool", params=params) +# return response +# # if response.status_code == 200: +# # return response["_embedded"]["hal:tool"] +# # else: +# # logging.warning("Error getting tools") +# # logging.warning(f"Status code: {response.status_code}") + +# def getTool(self, tool_id, params={}): +# response = self.baseRequest("tool/" + str(tool_id), params=params) +# if response.status_code == 200: +# return response.json() +# else: +# logging.warning("Error getting tool") +# logging.warning(f"Status code: {response.status_code}") + +# """ +# Lookup targets for a given tool +# Args: +# tool_id: +# The tool to lookup targets. +# search: +# The search criteria. It could be: +# - Empty: If empty, return custom targets associated to the tool. +# - Postal code: Return targets matched by postal code. +# - Lat/Long: Latitude and Longitude pair separated by '::'. +# Ex. 45.451596::-73.59912099999997. It will return targets +# matched for those coordinates. +# - Search term: For your csv tools, this will return targets +# matched by given valid search term. +# params: +# Query params for request. +# Returns: +# Targets information. +# """ + +# def lookupTargets(self, tool_id, search=None, params={}): +# endpoint = "lookup/" + str(tool_id) +# if search: +# endpoint += "/" + search +# response = self.baseRequest(endpoint, params=params) +# if response.status_code == 200: +# return response.json() +# else: +# logging.warning("Error looking up for targets") +# logging.warning(f"Status code: {response.status_code}") + +# """ +# Get action information. +# Args: +# tool_id: +# The tool to retrieve. +# Returns: +# Action information including structure to run the action. +# """ + +# def getAction(self, tool_id, params={}): +# response = self.baseRequest("action/" + str(tool_id), params=params) + +# if response.status_code == 200: +# return response.json() +# else: +# logging.warning("Error getting action") +# logging.warning(f"Status code: {response.status_code}") + +# """ +# Run specific action +# Args: +# tool_id: +# The tool to run. +# payload: +# The data to post to run the action. +# This data structure will depend on what has been +# returned by getAction. +# Returns: +# Posted outreach information. +# """ + +# def runAction(self, tool_id, payload, params={}): +# response = self.baseRequest( +# "action/" + str(tool_id), "PATCH", json.dumps(payload), params=params +# ) +# if response.status_code == 200: +# return response.json() +# else: +# logging.warning("Error running action") +# logging.warning(f"Status code: {response.status_code}") + +# def getTarget(self, target_id, params={}): +# response = self.baseRequest("target/" + str(target_id), params=params) +# if response.status_code == 200: +# return response.json() +# else: +# logging.warning("Error getting target") +# logging.warning(f"Status code: {response.status_code}") + +# def getCampaigns(self, params={}): +# response = self.baseRequest("campaign", params=params) +# if response.status_code == 200: +# return response.json()["_embedded"]["hal:campaign"] +# else: +# logging.warning("Error getting campaigns") +# logging.warning(f"Status code: {response.status_code}") + +# def getCampaign(self, campaign_id, params={}): +# response = self.baseRequest("campaign/" + str(campaign_id), params=params) +# if response.status_code == 200: +# return response.json() +# else: +# logging.warning("Error getting campaign") +# logging.warning(f"Status code: {response.status_code}") + +# def getOrganizations(self, params={}): +# response = self.baseRequest("organization", params=params) +# if response.status_code == 200: +# return response.json()["data"] +# else: +# logging.warning("Error getting organizations") +# logging.warning(f"Status code: {response.status_code}") + +# def getOrganization(self, organization_id, params={}): +# response = self.baseRequest( +# "organization/" + str(organization_id), params=params +# ) +# if response.status_code == 200: +# return response.json()["data"] +# else: +# logging.warning("Error getting organization") +# logging.warning(f"Status code: {response.status_code}") + +# def getServices(self, params={}): +# response = self.baseRequest("service", params=params) +# if response.status_code == 200: +# return response.json()["_embedded"]["hal:service"] +# else: +# logging.warning("Error getting services") +# logging.warning(f"Status code: {response.status_code}") + +# def getService(self, service_id, params={}): +# response = self.baseRequest("service/" + str(service_id), params=params) +# if response.status_code == 200: +# return response.json() +# else: +# logging.warning("Error getting service") +# logging.warning(f"Status code: {response.status_code}") + +# def getTargets(self, params={}): +# response = self.baseRequest("target", params=params) +# if response.status_code == 200: +# return response.json()["_embedded"]["hal:target"] +# else: +# logging.warning("Error getting targets") +# logging.warning(f"Status code: {response.status_code}") + +# def getOutreaches(self, tool_id, params={}): +# params["nid"] = str(tool_id) +# error_count = 0 +# while error_count < 10: +# try: +# response = self.baseRequest( +# "outreach", requires_csrf=False, params=params +# ) +# if response.status_code == 200: +# return response.json()["_embedded"]["hal:outreach"] +# elif response.status_code == 429: +# logging.warning(f"{response.status_code}: {response.text}") +# logging.warning(f"Pausing to avoid rate limiting...") +# time.sleep(20) +# else: +# logging.warning("Error getting outreaches") +# logging.warning(f"Status code: {response.status_code}") +# logging.warning(f"Status message: {response.text}") +# error_count += 1 +# time.sleep(20) +# except Exception as e: +# logging.warning(f"Exception: {e}") +# break + +# def getOutreach(self, outreach_id, params={}): +# response = self.baseRequest("outreach/" + str(outreach_id), params=params) +# if response.status_code == 200: +# return response.json() +# else: +# logging.warning("Error getting outreach") +# logging.warning(f"Status code: {response.status_code}") + +# # logger = logging.getLogger(__name__) + +# # API_URL = "https://engage.newmode.net/api/" + +# # class Newmode: +# # def __init__(self, api_user=None, api_password=None, api_version=None): +# # """ +# # Args: +# # api_user: str +# # The Newmode api user. Not required if ``NEWMODE_API_USER`` env variable is +# # passed. +# # api_password: str +# # The Newmode api password. Not required if ``NEWMODE_API_PASSWORD`` env variable is +# # passed. +# # api_version: str +# # The Newmode api version. Defaults to "v1.0" or the value of ``NEWMODE_API_VERSION`` +# # env variable. +# # Returns: +# # Newmode class +# # """ +# # self.api_user = check_env.check("NEWMODE_API_USER", api_user) +# # self.api_password = check_env.check("NEWMODE_API_PASSWORD", api_password) + +# # if api_version is None: +# # api_version = "v1.0" + +# # self.api_version = check_env.check("NEWMODE_API_VERSION", api_version) + +# # self.client = Client(api_user, api_password, api_version) + +# # def convert_to_table(self, data): +# # # Internal method to create a Parsons table from a data element. +# # table = None +# # if type(data) is list: +# # table = Table(data) +# # else: +# # table = Table([data]) + +# # return table + +# # def get_tools(self, params={}): +# # """ +# # Get existing tools. +# # Args: +# # params: +# # Extra parameters sent to New/Mode library. +# # Returns: +# # Tools information as table. +# # """ +# # tools = self.client.getTools(params=params) +# # if tools: +# # return self.convert_to_table(tools) +# # else: +# # logging.warning("Empty tools returned") +# # return self.convert_to_table([]) + +# # def get_tool(self, tool_id, params={}): +# # """ +# # Get specific tool. +# # Args: +# # tool_id: +# # The id of the tool to return. +# # params: +# # Extra parameters sent to New/Mode library. +# # Returns: +# # Tool information. +# # """ +# # tool = self.client.getTool(tool_id, params=params) +# # if tool: +# # return tool +# # else: +# # logging.warning("Empty tool returned") +# # return None + +# # def lookup_targets(self, tool_id, search=None, params={}): +# # """ +# # Lookup targets for a given tool +# # Args: +# # tool_id: +# # The tool to lookup targets. +# # search: +# # The search criteria. It could be: +# # - Empty: If empty, return custom targets associated to the tool. +# # - Postal code: Return targets matched by postal code. +# # - Lat/Long: Latitude and Longitude pair separated by '::'. +# # Ex. 45.451596::-73.59912099999997. It will return targets +# # matched for those coordinates. +# # - Search term: For your csv tools, this will return targets +# # matched by given valid search term. +# # Returns: +# # Targets information as table. +# # """ +# # targets = self.client.lookupTargets(tool_id, search, params=params) +# # if targets: +# # data = [] +# # for key in targets: +# # if key != "_links": +# # data.append(targets[key]) +# # return self.convert_to_table(data) +# # else: +# # logging.warning("Empty targets returned") +# # return self.convert_to_table([]) + +# # def get_action(self, tool_id, params={}): +# # """ +# # Get the action information for a given tool. +# # Args: +# # tool_id: +# # The id of the tool to return. +# # params: +# # Extra parameters sent to New/Mode library. +# # Returns: +# # Tool action information. +# # """ +# # action = self.client.getAction(tool_id, params=params) +# # if action: +# # return action +# # else: +# # logging.warning("Empty action returned") +# # return None + +# # def run_action(self, tool_id, payload, params={}): +# # """ +# # Run specific action with given payload. +# # Args: +# # tool_id: +# # The id of the tool to run. +# # payload: +# # Payload data used to run the action. Structure will depend +# # on the stuff returned by get_action. +# # params: +# # Extra parameters sent to New/Mode library. +# # Returns: +# # Action link (if otl) or sid. +# # """ +# # action = self.client.runAction(tool_id, payload, params=params) +# # if action: +# # if "link" in action: +# # return action["link"] +# # else: +# # return action["sid"] +# # else: +# # logging.warning("Error in response") +# # return None + +# # def get_target(self, target_id, params={}): +# # """ +# # Get specific target. +# # Args: +# # target_id: +# # The id of the target to return. +# # params: +# # Extra parameters sent to New/Mode library. +# # Returns: +# # Target information. +# # """ +# # target = self.client.getTarget(target_id, params=params) +# # if target: +# # return target +# # else: +# # logging.warning("Empty target returned") +# # return None + +# # def get_targets(self, params={}): +# # """ +# # Get all targets + +# # Args: +# # params dict: +# # Extra paramaters sent to New/Mode library + +# # Returns: +# # Target information +# # """ + +# # targets = self.client.getTargets(params=params) + +# # if targets: +# # return self.convert_to_table(targets) + +# # else: +# # logging.warning("No targets returned") +# # return None + +# # def get_campaigns(self, params={}): +# # """ +# # Get existing campaigns. +# # Args: +# # params: +# # Extra parameters sent to New/Mode library. +# # Returns: +# # Campaigns information as table. +# # """ +# # campaigns = self.client.getCampaigns(params=params) +# # if campaigns: +# # return self.convert_to_table(campaigns) +# # else: +# # logging.warning("Empty campaigns returned") +# # return self.convert_to_table([]) + +# # def get_campaign(self, campaign_id, params={}): +# # """ +# # Get specific campaign. +# # Args: +# # campaign_id: +# # The id of the campaign to return. +# # params: +# # Extra parameters sent to New/Mode library. +# # Returns: +# # Campaign information. +# # """ +# # campaign = self.client.getCampaign(campaign_id, params=params) +# # if campaign: +# # return campaign +# # else: +# # logging.warning("Empty campaign returned") +# # return None + +# # def get_organizations(self, params={}): +# # """ +# # Get existing organizations. +# # Args: +# # params: +# # Extra parameters sent to New/Mode library. +# # Returns: +# # Organizations information as table. +# # """ +# # organizations = self.client.getOrganizations(params=params) +# # if organizations: +# # return self.convert_to_table(organizations) +# # else: +# # logging.warning("Empty organizations returned") +# # return self.convert_to_table([]) + +# # def get_organization(self, organization_id, params={}): +# # """ +# # Get specific organization. +# # Args: +# # organization_id: +# # The id of the organization to return. +# # params: +# # Extra parameters sent to New/Mode library. +# # Returns: +# # Organization information. +# # """ +# # organization = self.client.getOrganization(organization_id, params=params) +# # if organization: +# # return organization +# # else: +# # logging.warning("Empty organization returned") +# # return None + +# # def get_services(self, params={}): +# # """ +# # Get existing services. +# # Args: +# # params: +# # Extra parameters sent to New/Mode library. +# # Returns: +# # Services information as table. +# # """ +# # services = self.client.getServices(params=params) +# # if services: +# # return self.convert_to_table(services) +# # else: +# # logging.warning("Empty services returned") +# # return self.convert_to_table([]) + +# # def get_service(self, service_id, params={}): +# # """ +# # Get specific service. +# # Args: +# # service_id: +# # The id of the service to return. +# # params: +# # Extra parameters sent to New/Mode library. +# # Returns: +# # Service information. +# # """ +# # service = self.client.getService(service_id, params=params) +# # if service: +# # return service +# # else: +# # logging.warning("Empty service returned") +# # return None + +# # def get_outreaches(self, tool_id, params={}): +# # """ +# # Get existing outreaches for a given tool. +# # Args: +# # tool_id: +# # Tool to return outreaches. +# # params: +# # Extra parameters sent to New/Mode library. +# # Returns: +# # Outreaches information as table. +# # """ +# # outreaches = self.client.getOutreaches(tool_id, params=params) +# # if outreaches: +# # return self.convert_to_table(outreaches) +# # else: +# # logging.warning("Empty outreaches returned") +# # return self.convert_to_table([]) + +# # def get_outreach(self, outreach_id, params={}): +# # """ +# # Get specific outreach. +# # Args: +# # outreach_id: +# # The id of the outreach to return. +# # params: +# # Extra parameters sent to New/Mode library. +# # Returns: +# # Outreach information. +# # """ +# # outreach = self.client.getOutreach(outreach_id, params=params) +# # if outreach: +# # return outreach +# # else: +# # logging.warning("Empty outreach returned") +# # return None From dfc2f1449056e429afd31e93f20f8da3e88f0522 Mon Sep 17 00:00:00 2001 From: sharinetmc Date: Tue, 19 Nov 2024 14:49:59 -1000 Subject: [PATCH 02/10] improve retry logic --- parsons/newmode/newmode.py | 57 ++++++++++++++++++++++---------------- 1 file changed, 33 insertions(+), 24 deletions(-) diff --git a/parsons/newmode/newmode.py b/parsons/newmode/newmode.py index 5fdfa571f6..2cf765d33f 100644 --- a/parsons/newmode/newmode.py +++ b/parsons/newmode/newmode.py @@ -1,5 +1,6 @@ from parsons.utilities.api_connector import APIConnector from parsons.utilities import check_env +from parsons import Table import logging import time @@ -56,29 +57,37 @@ def base_request( response = self.client.get_request(url=url, params=params) elif method == "PATCH": response = self.client.patch_request(url=url, params=params) - return response + # response.get("_embedded", {}).get(f"osdi:{object_name}") + try: + tbl = Table(response) + except Exception as e: + logger.error("Failed to convert API response json to Parsons Table") + raise e + return tbl - def get_csrf_token(self, max_attempts=10): + def get_csrf_token(self, max_retries-10): """ Retrieve a CSRF token for making API requests `Args:` - max_attempts: int + max_retries: int The maximum number of attempts to get the CSRF token. `Returns:` The CSRF token. """ - for attempt in range(max_attempts): + for attempt in range(max_retries): try: response = self.base_request( endpoint="session/token", method="GET", requires_csrf=False ) return response except Exception as e: + if attempt >= max_retries: + logger.error((f"Error getting CSRF Token after {max_retries} retries")) + raise e logger.warning( - f"Attempt {attempt} at getting CSRF Token failed. Retrying. Error: {e}" + f"Retry {attempt} at getting CSRF Token failed. Retrying. Error: {e}" ) time.sleep(attempt + 1) - logger.warning(f"Error getting CSRF Token after {max_attempts} attempts") def get_tools(self, params={}): """ @@ -87,7 +96,7 @@ def get_tools(self, params={}): params: dict Query parameters to include in the request. `Returns:` - Response object from the API request containing tools data. + Parsons Table containing tools data. """ response = self.base_request(endpoint="tool", method="GET", params=params) return response @@ -101,7 +110,7 @@ def get_tool(self, tool_id, params={}): params: dict Query parameters to include in the request. `Returns:` - Response object from the API request containing the tool data. + Parsons Table containing the tool data. """ response = self.base_request( endpoint=f"tool/{tool_id}", method="GET", params=params @@ -119,7 +128,7 @@ def lookup_targets(self, tool_id, search=None, params={}): params: dict Query parameters to include in the request. `Returns:` - Response object from the API request containing target data. + Parsons Table containing target data. """ endpoint = f"lookup/{tool_id}" if search: @@ -136,7 +145,7 @@ def get_action(self, tool_id, params={}): params: dict Query parameters to include in the request. `Returns:` - Response object from the API request containing action data. + Parsons Table containing action data. """ response = self.base_request( endpoint=f"action/{tool_id}", method="GET", params=params @@ -154,7 +163,7 @@ def run_action(self, tool_id, payload, params={}): params: dict Query parameters to include in the request. `Returns:` - Response object from the API request containing posted outreach information. + Parsons Table containing posted outreach information. """ response = self.base_request( endpoint=f"action/{tool_id}", method="PATCH", payload=payload, params=params @@ -170,7 +179,7 @@ def get_target(self, target_id, params={}): params: dict Query parameters to include in the request. `Returns:` - Response object from the API request containing target data. + Parsons Table containing target data. """ response = self.base_request( endpoint=f"target/{target_id}", method="GET", params=params @@ -184,7 +193,7 @@ def get_campaigns(self, params={}): params: dict Query parameters to include in the request. `Returns:` - Response object from the API request containing campaigns data. + Parsons Table containing campaigns data. """ response = self.base_request(endpoint="campaign", method="GET", params=params) return response @@ -198,7 +207,7 @@ def get_campaign(self, campaign_id, params={}): params: dict Query parameters to include in the request. `Returns:` - Response object from the API request containing campaign data. + Parsons Table containing campaign data. """ response = self.base_request( endpoint=f"campaign/{campaign_id}", method="GET", params=params @@ -212,7 +221,7 @@ def get_organizations(self, params={}): params: dict Query parameters to include in the request. `Returns:` - Response object from the API request containing organizations data. + Parsons Table containing organizations data. """ response = self.base_request( endpoint="organization", method="GET", params=params @@ -228,7 +237,7 @@ def get_organization(self, organization_id, params={}): params: dict Query parameters to include in the request. `Returns:` - Response object from the API request containing organization data. + Parsons Table containing organization data. """ response = self.base_request( endpoint=f"organization/{organization_id}", method="GET", params=params @@ -242,7 +251,7 @@ def get_services(self, params={}): params: dict Query parameters to include in the request. `Returns:` - Response object from the API request containing services data. + Parsons Table containing services data. """ response = self.base_request(endpoint="service", method="GET", params=params) return response @@ -256,7 +265,7 @@ def get_service(self, service_id, params={}): params: dict Query parameters to include in the request. `Returns:` - Response object from the API request containing service data. + Parsons Table containing service data. """ response = self.base_request( endpoint=f"service/{service_id}", method="GET", params=params @@ -270,7 +279,7 @@ def get_targets(self, params={}): params: dict Query parameters to include in the request. `Returns:` - Response object from the API request containing targets data. + Parsons Table containing targets data. """ response = self.base_request(endpoint="target", method="GET", params=params) return response @@ -284,7 +293,7 @@ def get_outreaches(self, tool_id, params={}): params: dict Query parameters to include in the request. `Returns:` - Response object from the API request containing outreaches data. + Parsons Table containing outreaches data. """ params["nid"] = str(tool_id) response = self.base_request( @@ -301,7 +310,7 @@ def get_outreach(self, outreach_id, params={}): params: dict Query parameters to include in the request. `Returns:` - Response object from the API request containing outreach data. + Parsons Table containing outreach data. """ response = self.base_request( endpoint=f"outreach/{outreach_id}", method="GET", params=params @@ -364,14 +373,14 @@ def get_outreach(self, outreach_id, params={}): # self.headers["X-CSRF-Token"] = csrf # return self.client.get_request(url=url, params=params) -# def get_csrf_token(self, max_attempts=10): -# for attempt in range(max_attempts): +# def get_csrf_token(self, max_retries=10): +# for attempt in range(max_retries): # try: # response = self.base_request(endpoint="session/token", requires_csrf=False) # return response # except Exception as e: # logger.warning(f"Attempt {attempt} at getting CSRF Token failed. Retrying. Error: {e}") -# logger.warning(f"Error getting CSRF Token after {max_attempts} attempts") +# logger.warning(f"Error getting CSRF Token after {max_retries} attempts") # def get_tools(self, params={}): # response = self.baseRequest(endpoint="tool", params=params) From 7390fe96527702e39daf422b08b1a915696af5bc Mon Sep 17 00:00:00 2001 From: sharinetmc Date: Tue, 19 Nov 2024 14:50:20 -1000 Subject: [PATCH 03/10] typo --- parsons/newmode/newmode.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/parsons/newmode/newmode.py b/parsons/newmode/newmode.py index 2cf765d33f..4bc5f0a64c 100644 --- a/parsons/newmode/newmode.py +++ b/parsons/newmode/newmode.py @@ -65,7 +65,7 @@ def base_request( raise e return tbl - def get_csrf_token(self, max_retries-10): + def get_csrf_token(self, max_retries=10): """ Retrieve a CSRF token for making API requests `Args:` From 8194dfd0cb751e5b7d957ce226b3cab42c1e1119 Mon Sep 17 00:00:00 2001 From: sharinetmc Date: Tue, 19 Nov 2024 14:56:12 -1000 Subject: [PATCH 04/10] clean --- parsons/newmode/newmode.py | 575 +------------------------------------ 1 file changed, 1 insertion(+), 574 deletions(-) diff --git a/parsons/newmode/newmode.py b/parsons/newmode/newmode.py index 4bc5f0a64c..a1c822f4fd 100644 --- a/parsons/newmode/newmode.py +++ b/parsons/newmode/newmode.py @@ -316,577 +316,4 @@ def get_outreach(self, outreach_id, params={}): endpoint=f"outreach/{outreach_id}", method="GET", params=params ) return response - - -# from parsons.utilities.api_connector import APIConnector -# from parsons.utilities import check_env -# from parsons.etl import Table -# import logging - -# logger = logging.getLogger(__name__) - -# API_URL = "https://engage.newmode.net/api/" - -# ########## - - -# class NewMode(object): -# """ -# Instantiate Class -# `Args`: -# api_user: -# The username to use for the API requests. Not required if ``NEWMODE_API_URL`` -# env variable set. -# api_password: -# The password to use for the API requests. Not required if ``NEWMODE_API_PASSWORD`` -# env variable set. -# api_version: -# The api version to use. Defaults to v1.0 -# Returns: -# NewMode Class -# """ - -# def __init__(self, api_user=None, api_password=None, api_version="v2.1"): -# self.base_url = check_env.check("NEWMODE_API_URL", API_URL) -# self.api_user = check_env.check("NEWMODE_API_USER", api_user) -# self.api_password = check_env.check("NEWMODE_API_PASSWORD", api_password) -# self.api_version = check_env.check("NEWMODE_API_VERSION", api_version) -# self.headers = {"Content-Type": "application/json"} -# self.url = f"{self.base_url}{self.api_version}/" -# self.client = APIConnector( -# self.api_url_with_version, -# auth=(self.api_user, self.api_password), -# headers=self.headers, -# ) - -# def base_request( -# self, -# endpoint, -# requires_csrf=True, -# params={}, -# ): - -# url = endpoint - -# if requires_csrf: -# csrf = self.get_csrf_token() -# self.headers["X-CSRF-Token"] = csrf -# return self.client.get_request(url=url, params=params) - -# def get_csrf_token(self, max_retries=10): -# for attempt in range(max_retries): -# try: -# response = self.base_request(endpoint="session/token", requires_csrf=False) -# return response -# except Exception as e: -# logger.warning(f"Attempt {attempt} at getting CSRF Token failed. Retrying. Error: {e}") -# logger.warning(f"Error getting CSRF Token after {max_retries} attempts") - -# def get_tools(self, params={}): -# response = self.baseRequest(endpoint="tool", params=params) -# return response -# # if response.status_code == 200: -# # return response["_embedded"]["hal:tool"] -# # else: -# # logging.warning("Error getting tools") -# # logging.warning(f"Status code: {response.status_code}") - -# def getTool(self, tool_id, params={}): -# response = self.baseRequest("tool/" + str(tool_id), params=params) -# if response.status_code == 200: -# return response.json() -# else: -# logging.warning("Error getting tool") -# logging.warning(f"Status code: {response.status_code}") - -# """ -# Lookup targets for a given tool -# Args: -# tool_id: -# The tool to lookup targets. -# search: -# The search criteria. It could be: -# - Empty: If empty, return custom targets associated to the tool. -# - Postal code: Return targets matched by postal code. -# - Lat/Long: Latitude and Longitude pair separated by '::'. -# Ex. 45.451596::-73.59912099999997. It will return targets -# matched for those coordinates. -# - Search term: For your csv tools, this will return targets -# matched by given valid search term. -# params: -# Query params for request. -# Returns: -# Targets information. -# """ - -# def lookupTargets(self, tool_id, search=None, params={}): -# endpoint = "lookup/" + str(tool_id) -# if search: -# endpoint += "/" + search -# response = self.baseRequest(endpoint, params=params) -# if response.status_code == 200: -# return response.json() -# else: -# logging.warning("Error looking up for targets") -# logging.warning(f"Status code: {response.status_code}") - -# """ -# Get action information. -# Args: -# tool_id: -# The tool to retrieve. -# Returns: -# Action information including structure to run the action. -# """ - -# def getAction(self, tool_id, params={}): -# response = self.baseRequest("action/" + str(tool_id), params=params) - -# if response.status_code == 200: -# return response.json() -# else: -# logging.warning("Error getting action") -# logging.warning(f"Status code: {response.status_code}") - -# """ -# Run specific action -# Args: -# tool_id: -# The tool to run. -# payload: -# The data to post to run the action. -# This data structure will depend on what has been -# returned by getAction. -# Returns: -# Posted outreach information. -# """ - -# def runAction(self, tool_id, payload, params={}): -# response = self.baseRequest( -# "action/" + str(tool_id), "PATCH", json.dumps(payload), params=params -# ) -# if response.status_code == 200: -# return response.json() -# else: -# logging.warning("Error running action") -# logging.warning(f"Status code: {response.status_code}") - -# def getTarget(self, target_id, params={}): -# response = self.baseRequest("target/" + str(target_id), params=params) -# if response.status_code == 200: -# return response.json() -# else: -# logging.warning("Error getting target") -# logging.warning(f"Status code: {response.status_code}") - -# def getCampaigns(self, params={}): -# response = self.baseRequest("campaign", params=params) -# if response.status_code == 200: -# return response.json()["_embedded"]["hal:campaign"] -# else: -# logging.warning("Error getting campaigns") -# logging.warning(f"Status code: {response.status_code}") - -# def getCampaign(self, campaign_id, params={}): -# response = self.baseRequest("campaign/" + str(campaign_id), params=params) -# if response.status_code == 200: -# return response.json() -# else: -# logging.warning("Error getting campaign") -# logging.warning(f"Status code: {response.status_code}") - -# def getOrganizations(self, params={}): -# response = self.baseRequest("organization", params=params) -# if response.status_code == 200: -# return response.json()["data"] -# else: -# logging.warning("Error getting organizations") -# logging.warning(f"Status code: {response.status_code}") - -# def getOrganization(self, organization_id, params={}): -# response = self.baseRequest( -# "organization/" + str(organization_id), params=params -# ) -# if response.status_code == 200: -# return response.json()["data"] -# else: -# logging.warning("Error getting organization") -# logging.warning(f"Status code: {response.status_code}") - -# def getServices(self, params={}): -# response = self.baseRequest("service", params=params) -# if response.status_code == 200: -# return response.json()["_embedded"]["hal:service"] -# else: -# logging.warning("Error getting services") -# logging.warning(f"Status code: {response.status_code}") - -# def getService(self, service_id, params={}): -# response = self.baseRequest("service/" + str(service_id), params=params) -# if response.status_code == 200: -# return response.json() -# else: -# logging.warning("Error getting service") -# logging.warning(f"Status code: {response.status_code}") - -# def getTargets(self, params={}): -# response = self.baseRequest("target", params=params) -# if response.status_code == 200: -# return response.json()["_embedded"]["hal:target"] -# else: -# logging.warning("Error getting targets") -# logging.warning(f"Status code: {response.status_code}") - -# def getOutreaches(self, tool_id, params={}): -# params["nid"] = str(tool_id) -# error_count = 0 -# while error_count < 10: -# try: -# response = self.baseRequest( -# "outreach", requires_csrf=False, params=params -# ) -# if response.status_code == 200: -# return response.json()["_embedded"]["hal:outreach"] -# elif response.status_code == 429: -# logging.warning(f"{response.status_code}: {response.text}") -# logging.warning(f"Pausing to avoid rate limiting...") -# time.sleep(20) -# else: -# logging.warning("Error getting outreaches") -# logging.warning(f"Status code: {response.status_code}") -# logging.warning(f"Status message: {response.text}") -# error_count += 1 -# time.sleep(20) -# except Exception as e: -# logging.warning(f"Exception: {e}") -# break - -# def getOutreach(self, outreach_id, params={}): -# response = self.baseRequest("outreach/" + str(outreach_id), params=params) -# if response.status_code == 200: -# return response.json() -# else: -# logging.warning("Error getting outreach") -# logging.warning(f"Status code: {response.status_code}") - -# # logger = logging.getLogger(__name__) - -# # API_URL = "https://engage.newmode.net/api/" - -# # class Newmode: -# # def __init__(self, api_user=None, api_password=None, api_version=None): -# # """ -# # Args: -# # api_user: str -# # The Newmode api user. Not required if ``NEWMODE_API_USER`` env variable is -# # passed. -# # api_password: str -# # The Newmode api password. Not required if ``NEWMODE_API_PASSWORD`` env variable is -# # passed. -# # api_version: str -# # The Newmode api version. Defaults to "v1.0" or the value of ``NEWMODE_API_VERSION`` -# # env variable. -# # Returns: -# # Newmode class -# # """ -# # self.api_user = check_env.check("NEWMODE_API_USER", api_user) -# # self.api_password = check_env.check("NEWMODE_API_PASSWORD", api_password) - -# # if api_version is None: -# # api_version = "v1.0" - -# # self.api_version = check_env.check("NEWMODE_API_VERSION", api_version) - -# # self.client = Client(api_user, api_password, api_version) - -# # def convert_to_table(self, data): -# # # Internal method to create a Parsons table from a data element. -# # table = None -# # if type(data) is list: -# # table = Table(data) -# # else: -# # table = Table([data]) - -# # return table - -# # def get_tools(self, params={}): -# # """ -# # Get existing tools. -# # Args: -# # params: -# # Extra parameters sent to New/Mode library. -# # Returns: -# # Tools information as table. -# # """ -# # tools = self.client.getTools(params=params) -# # if tools: -# # return self.convert_to_table(tools) -# # else: -# # logging.warning("Empty tools returned") -# # return self.convert_to_table([]) - -# # def get_tool(self, tool_id, params={}): -# # """ -# # Get specific tool. -# # Args: -# # tool_id: -# # The id of the tool to return. -# # params: -# # Extra parameters sent to New/Mode library. -# # Returns: -# # Tool information. -# # """ -# # tool = self.client.getTool(tool_id, params=params) -# # if tool: -# # return tool -# # else: -# # logging.warning("Empty tool returned") -# # return None - -# # def lookup_targets(self, tool_id, search=None, params={}): -# # """ -# # Lookup targets for a given tool -# # Args: -# # tool_id: -# # The tool to lookup targets. -# # search: -# # The search criteria. It could be: -# # - Empty: If empty, return custom targets associated to the tool. -# # - Postal code: Return targets matched by postal code. -# # - Lat/Long: Latitude and Longitude pair separated by '::'. -# # Ex. 45.451596::-73.59912099999997. It will return targets -# # matched for those coordinates. -# # - Search term: For your csv tools, this will return targets -# # matched by given valid search term. -# # Returns: -# # Targets information as table. -# # """ -# # targets = self.client.lookupTargets(tool_id, search, params=params) -# # if targets: -# # data = [] -# # for key in targets: -# # if key != "_links": -# # data.append(targets[key]) -# # return self.convert_to_table(data) -# # else: -# # logging.warning("Empty targets returned") -# # return self.convert_to_table([]) - -# # def get_action(self, tool_id, params={}): -# # """ -# # Get the action information for a given tool. -# # Args: -# # tool_id: -# # The id of the tool to return. -# # params: -# # Extra parameters sent to New/Mode library. -# # Returns: -# # Tool action information. -# # """ -# # action = self.client.getAction(tool_id, params=params) -# # if action: -# # return action -# # else: -# # logging.warning("Empty action returned") -# # return None - -# # def run_action(self, tool_id, payload, params={}): -# # """ -# # Run specific action with given payload. -# # Args: -# # tool_id: -# # The id of the tool to run. -# # payload: -# # Payload data used to run the action. Structure will depend -# # on the stuff returned by get_action. -# # params: -# # Extra parameters sent to New/Mode library. -# # Returns: -# # Action link (if otl) or sid. -# # """ -# # action = self.client.runAction(tool_id, payload, params=params) -# # if action: -# # if "link" in action: -# # return action["link"] -# # else: -# # return action["sid"] -# # else: -# # logging.warning("Error in response") -# # return None - -# # def get_target(self, target_id, params={}): -# # """ -# # Get specific target. -# # Args: -# # target_id: -# # The id of the target to return. -# # params: -# # Extra parameters sent to New/Mode library. -# # Returns: -# # Target information. -# # """ -# # target = self.client.getTarget(target_id, params=params) -# # if target: -# # return target -# # else: -# # logging.warning("Empty target returned") -# # return None - -# # def get_targets(self, params={}): -# # """ -# # Get all targets - -# # Args: -# # params dict: -# # Extra paramaters sent to New/Mode library - -# # Returns: -# # Target information -# # """ - -# # targets = self.client.getTargets(params=params) - -# # if targets: -# # return self.convert_to_table(targets) - -# # else: -# # logging.warning("No targets returned") -# # return None - -# # def get_campaigns(self, params={}): -# # """ -# # Get existing campaigns. -# # Args: -# # params: -# # Extra parameters sent to New/Mode library. -# # Returns: -# # Campaigns information as table. -# # """ -# # campaigns = self.client.getCampaigns(params=params) -# # if campaigns: -# # return self.convert_to_table(campaigns) -# # else: -# # logging.warning("Empty campaigns returned") -# # return self.convert_to_table([]) - -# # def get_campaign(self, campaign_id, params={}): -# # """ -# # Get specific campaign. -# # Args: -# # campaign_id: -# # The id of the campaign to return. -# # params: -# # Extra parameters sent to New/Mode library. -# # Returns: -# # Campaign information. -# # """ -# # campaign = self.client.getCampaign(campaign_id, params=params) -# # if campaign: -# # return campaign -# # else: -# # logging.warning("Empty campaign returned") -# # return None - -# # def get_organizations(self, params={}): -# # """ -# # Get existing organizations. -# # Args: -# # params: -# # Extra parameters sent to New/Mode library. -# # Returns: -# # Organizations information as table. -# # """ -# # organizations = self.client.getOrganizations(params=params) -# # if organizations: -# # return self.convert_to_table(organizations) -# # else: -# # logging.warning("Empty organizations returned") -# # return self.convert_to_table([]) - -# # def get_organization(self, organization_id, params={}): -# # """ -# # Get specific organization. -# # Args: -# # organization_id: -# # The id of the organization to return. -# # params: -# # Extra parameters sent to New/Mode library. -# # Returns: -# # Organization information. -# # """ -# # organization = self.client.getOrganization(organization_id, params=params) -# # if organization: -# # return organization -# # else: -# # logging.warning("Empty organization returned") -# # return None - -# # def get_services(self, params={}): -# # """ -# # Get existing services. -# # Args: -# # params: -# # Extra parameters sent to New/Mode library. -# # Returns: -# # Services information as table. -# # """ -# # services = self.client.getServices(params=params) -# # if services: -# # return self.convert_to_table(services) -# # else: -# # logging.warning("Empty services returned") -# # return self.convert_to_table([]) - -# # def get_service(self, service_id, params={}): -# # """ -# # Get specific service. -# # Args: -# # service_id: -# # The id of the service to return. -# # params: -# # Extra parameters sent to New/Mode library. -# # Returns: -# # Service information. -# # """ -# # service = self.client.getService(service_id, params=params) -# # if service: -# # return service -# # else: -# # logging.warning("Empty service returned") -# # return None - -# # def get_outreaches(self, tool_id, params={}): -# # """ -# # Get existing outreaches for a given tool. -# # Args: -# # tool_id: -# # Tool to return outreaches. -# # params: -# # Extra parameters sent to New/Mode library. -# # Returns: -# # Outreaches information as table. -# # """ -# # outreaches = self.client.getOutreaches(tool_id, params=params) -# # if outreaches: -# # return self.convert_to_table(outreaches) -# # else: -# # logging.warning("Empty outreaches returned") -# # return self.convert_to_table([]) - -# # def get_outreach(self, outreach_id, params={}): -# # """ -# # Get specific outreach. -# # Args: -# # outreach_id: -# # The id of the outreach to return. -# # params: -# # Extra parameters sent to New/Mode library. -# # Returns: -# # Outreach information. -# # """ -# # outreach = self.client.getOutreach(outreach_id, params=params) -# # if outreach: -# # return outreach -# # else: -# # logging.warning("Empty outreach returned") -# # return None + \ No newline at end of file From d1ff157759cbfbc7368994f6547bc721c514bcb6 Mon Sep 17 00:00:00 2001 From: sharinetmc Date: Tue, 19 Nov 2024 21:43:50 -1000 Subject: [PATCH 05/10] clean up --- parsons/newmode/newmode.py | 122 +++++++++++++++++++++++++------------ 1 file changed, 83 insertions(+), 39 deletions(-) diff --git a/parsons/newmode/newmode.py b/parsons/newmode/newmode.py index a1c822f4fd..d3a2dc87e2 100644 --- a/parsons/newmode/newmode.py +++ b/parsons/newmode/newmode.py @@ -9,7 +9,7 @@ API_URL = "https://engage.newmode.net/api/" -class NewMode(object): +class Newmode(object): """ Instantiate Class `Args`: @@ -20,33 +20,47 @@ class NewMode(object): The password to use for the API requests. Not required if ``NEWMODE_API_PASSWORD`` env variable set. api_version: str - The api version to use. Defaults to v2.1 + The api version to use. Defaults to v1.0 Returns: NewMode Class """ - def __init__(self, api_user=None, api_password=None, api_version="v2.1"): + def __init__(self, api_user=None, api_password=None, api_version="v1.0"): self.base_url = check_env.check("NEWMODE_API_URL", API_URL) self.api_user = check_env.check("NEWMODE_API_USER", api_user) self.api_password = check_env.check("NEWMODE_API_PASSWORD", api_password) self.api_version = check_env.check("NEWMODE_API_VERSION", api_version) self.headers = {"Content-Type": "application/json"} - self.url = f"{self.base_url}{self.api_version}/" self.client = APIConnector( - self.api_url_with_version, + self.base_url, auth=(self.api_user, self.api_password), headers=self.headers, ) - def base_request( - self, - endpoint, - method, - requires_csrf=True, - params={}, - ): + def check_api_version(self, documentation_url="TODO", v1_0=True, v2_1=True): + exception_text = f"Endpoint not supported by API version {self.api_version}" + if self.api_version == "v1.0": + logger.warning( + "Newmode API v1.0 will no longer be supported starting February 2025." + f"Documentation for v2.1 here: {documentation_url}" + ) + if not v1_0: + raise Exception(exception_text) + elif self.api_version == "v2.1" and not v2_1: + raise Exception(exception_text) + + def convert_to_table(self, data): + """Internal method to create a Parsons table from a data element.""" + + table = None + if type(data) is list: + table = Table(data) + else: + table = Table([data]) + + return table - url = endpoint + def base_request(self, method, url, requires_csrf=True, params={}): if requires_csrf: csrf = self.get_csrf_token() @@ -58,12 +72,28 @@ def base_request( elif method == "PATCH": response = self.client.patch_request(url=url, params=params) # response.get("_embedded", {}).get(f"osdi:{object_name}") - try: - tbl = Table(response) - except Exception as e: - logger.error("Failed to convert API response json to Parsons Table") - raise e - return tbl + return response + + def converted_request( + self, + endpoint, + method, + requires_csrf=True, + supports_version=True, + params={}, + convert_to_table=True, + v1_0=True, + v2_1=True, + ): + self.check_api_version(v1_0=v1_0, v2_1=v2_1) + url = f"{self.api_version}/{endpoint}" if supports_version else endpoint + response = self.base_request( + method=method, url=url, requires_csrf=requires_csrf, params=params + ) + if convert_to_table: + return self.convert_to_table(response) + else: + return response def get_csrf_token(self, max_retries=10): """ @@ -74,15 +104,22 @@ def get_csrf_token(self, max_retries=10): `Returns:` The CSRF token. """ + for attempt in range(max_retries): try: - response = self.base_request( - endpoint="session/token", method="GET", requires_csrf=False + response = self.converted_request( + endpoint="session/token", + method="GET", + supports_version=False, + requires_csrf=False, + convert_to_table=False, ) - return response + return response["X-CSRF-Token"] except Exception as e: if attempt >= max_retries: - logger.error((f"Error getting CSRF Token after {max_retries} retries")) + logger.error( + (f"Error getting CSRF Token after {max_retries} retries") + ) raise e logger.warning( f"Retry {attempt} at getting CSRF Token failed. Retrying. Error: {e}" @@ -98,7 +135,7 @@ def get_tools(self, params={}): `Returns:` Parsons Table containing tools data. """ - response = self.base_request(endpoint="tool", method="GET", params=params) + response = self.converted_request(endpoint="tool", method="GET", params=params) return response def get_tool(self, tool_id, params={}): @@ -112,7 +149,7 @@ def get_tool(self, tool_id, params={}): `Returns:` Parsons Table containing the tool data. """ - response = self.base_request( + response = self.converted_request( endpoint=f"tool/{tool_id}", method="GET", params=params ) return response @@ -133,7 +170,9 @@ def lookup_targets(self, tool_id, search=None, params={}): endpoint = f"lookup/{tool_id}" if search: endpoint += f"/{search}" - response = self.base_request(endpoint=endpoint, method="GET", params=params) + response = self.converted_request( + endpoint=endpoint, method="GET", params=params + ) return response def get_action(self, tool_id, params={}): @@ -147,7 +186,7 @@ def get_action(self, tool_id, params={}): `Returns:` Parsons Table containing action data. """ - response = self.base_request( + response = self.converted_request( endpoint=f"action/{tool_id}", method="GET", params=params ) return response @@ -165,7 +204,7 @@ def run_action(self, tool_id, payload, params={}): `Returns:` Parsons Table containing posted outreach information. """ - response = self.base_request( + response = self.converted_request( endpoint=f"action/{tool_id}", method="PATCH", payload=payload, params=params ) return response @@ -181,7 +220,7 @@ def get_target(self, target_id, params={}): `Returns:` Parsons Table containing target data. """ - response = self.base_request( + response = self.converted_request( endpoint=f"target/{target_id}", method="GET", params=params ) return response @@ -195,7 +234,9 @@ def get_campaigns(self, params={}): `Returns:` Parsons Table containing campaigns data. """ - response = self.base_request(endpoint="campaign", method="GET", params=params) + response = self.converted_request( + endpoint="campaign", method="GET", params=params + ) return response def get_campaign(self, campaign_id, params={}): @@ -209,7 +250,7 @@ def get_campaign(self, campaign_id, params={}): `Returns:` Parsons Table containing campaign data. """ - response = self.base_request( + response = self.converted_request( endpoint=f"campaign/{campaign_id}", method="GET", params=params ) return response @@ -223,7 +264,7 @@ def get_organizations(self, params={}): `Returns:` Parsons Table containing organizations data. """ - response = self.base_request( + response = self.converted_request( endpoint="organization", method="GET", params=params ) return response @@ -239,7 +280,7 @@ def get_organization(self, organization_id, params={}): `Returns:` Parsons Table containing organization data. """ - response = self.base_request( + response = self.converted_request( endpoint=f"organization/{organization_id}", method="GET", params=params ) return response @@ -253,7 +294,9 @@ def get_services(self, params={}): `Returns:` Parsons Table containing services data. """ - response = self.base_request(endpoint="service", method="GET", params=params) + response = self.converted_request( + endpoint="service", method="GET", params=params + ) return response def get_service(self, service_id, params={}): @@ -267,7 +310,7 @@ def get_service(self, service_id, params={}): `Returns:` Parsons Table containing service data. """ - response = self.base_request( + response = self.converted_request( endpoint=f"service/{service_id}", method="GET", params=params ) return response @@ -281,7 +324,9 @@ def get_targets(self, params={}): `Returns:` Parsons Table containing targets data. """ - response = self.base_request(endpoint="target", method="GET", params=params) + response = self.converted_request( + endpoint="target", method="GET", params=params + ) return response def get_outreaches(self, tool_id, params={}): @@ -296,7 +341,7 @@ def get_outreaches(self, tool_id, params={}): Parsons Table containing outreaches data. """ params["nid"] = str(tool_id) - response = self.base_request( + response = self.converted_request( endpoint="outreach", method="GET", requires_csrf=False, params=params ) return response @@ -312,8 +357,7 @@ def get_outreach(self, outreach_id, params={}): `Returns:` Parsons Table containing outreach data. """ - response = self.base_request( + response = self.converted_request( endpoint=f"outreach/{outreach_id}", method="GET", params=params ) return response - \ No newline at end of file From 8a45cbb35649f0ab687ed137e382e54c3a155929 Mon Sep 17 00:00:00 2001 From: sharinetmc Date: Mon, 9 Dec 2024 15:48:50 -1000 Subject: [PATCH 06/10] add oauth for v2 --- parsons/newmode/newmode.py | 189 +++++++++++++++++------ parsons/utilities/oauth_api_connector.py | 2 +- 2 files changed, 141 insertions(+), 50 deletions(-) diff --git a/parsons/newmode/newmode.py b/parsons/newmode/newmode.py index d3a2dc87e2..552221b7d6 100644 --- a/parsons/newmode/newmode.py +++ b/parsons/newmode/newmode.py @@ -1,4 +1,5 @@ from parsons.utilities.api_connector import APIConnector +from parsons.utilities.oauth_api_connector import OAuth2APIConnector from parsons.utilities import check_env from parsons import Table import logging @@ -6,8 +7,9 @@ logger = logging.getLogger(__name__) -API_URL = "https://engage.newmode.net/api/" - +API_URL_V1 = "https://engage.newmode.net/api/" +API_URL_V2 = "https://base.newmode.net/api/" +API_AUTH_URL = 'https://base.newmode.net/oauth/token' class Newmode(object): """ @@ -25,29 +27,34 @@ class Newmode(object): NewMode Class """ - def __init__(self, api_user=None, api_password=None, api_version="v1.0"): - self.base_url = check_env.check("NEWMODE_API_URL", API_URL) - self.api_user = check_env.check("NEWMODE_API_USER", api_user) - self.api_password = check_env.check("NEWMODE_API_PASSWORD", api_password) + def __init__(self, api_user=None, api_password=None, client_id=None, client_secret=None, api_version="v1.0"): self.api_version = check_env.check("NEWMODE_API_VERSION", api_version) - self.headers = {"Content-Type": "application/json"} - self.client = APIConnector( - self.base_url, - auth=(self.api_user, self.api_password), - headers=self.headers, - ) - def check_api_version(self, documentation_url="TODO", v1_0=True, v2_1=True): - exception_text = f"Endpoint not supported by API version {self.api_version}" - if self.api_version == "v1.0": + if "v1" in self.api_version: logger.warning( - "Newmode API v1.0 will no longer be supported starting February 2025." - f"Documentation for v2.1 here: {documentation_url}" + "Newmode API v1 will no longer be supported starting Feburary 2025." ) - if not v1_0: - raise Exception(exception_text) - elif self.api_version == "v2.1" and not v2_1: - raise Exception(exception_text) + self.base_url = API_URL_V1 + self.api_user = check_env.check("NEWMODE_API_USER", api_user) + self.api_password = check_env.check("NEWMODE_API_PASSWORD", api_password) + self.headers = {"Content-Type": "application/json"} + self.client = APIConnector( + self.base_url, + auth=(self.api_user, self.api_password), + headers=self.headers,) + else: + self.base_url = API_URL_V2 + self.client_id = check_env.check("NEWMODE_API_CLIENT_ID", client_id) + self.__client_secret = check_env.check("NEWMODE_API_CLIENT_SECRET", client_secret) + self.headers = {"content-type": "application/x-www-form-urlencoded"} + self.client = OAuth2APIConnector( + uri=self.base_url, + auto_refresh_url=API_AUTH_URL, + client_id=self.client_id, + client_secret=self.__client_secret, + headers=self.headers, + token_url=API_AUTH_URL, + grant_type="client_credentials",) def convert_to_table(self, data): """Internal method to create a Parsons table from a data element.""" @@ -69,6 +76,8 @@ def base_request(self, method, url, requires_csrf=True, params={}): response = None if method == "GET": response = self.client.get_request(url=url, params=params) + # if "targets" in url: + # response = self.client.get_request(url=url, params=params)['_embedded']['hal:tool'] elif method == "PATCH": response = self.client.patch_request(url=url, params=params) # response.get("_embedded", {}).get(f"osdi:{object_name}") @@ -82,19 +91,18 @@ def converted_request( supports_version=True, params={}, convert_to_table=True, - v1_0=True, - v2_1=True, ): - self.check_api_version(v1_0=v1_0, v2_1=v2_1) url = f"{self.api_version}/{endpoint}" if supports_version else endpoint response = self.base_request( method=method, url=url, requires_csrf=requires_csrf, params=params ) + if not response: + logging.warning(f"Empty result returned from endpoint: {endpoint}") if convert_to_table: return self.convert_to_table(response) else: return response - + def get_csrf_token(self, max_retries=10): """ Retrieve a CSRF token for making API requests @@ -104,11 +112,11 @@ def get_csrf_token(self, max_retries=10): `Returns:` The CSRF token. """ - + endpoint = "session/token" for attempt in range(max_retries): try: response = self.converted_request( - endpoint="session/token", + endpoint=endpoint, method="GET", supports_version=False, requires_csrf=False, @@ -128,6 +136,7 @@ def get_csrf_token(self, max_retries=10): def get_tools(self, params={}): """ + V1 only Retrieve all tools `Args:` params: dict @@ -140,6 +149,7 @@ def get_tools(self, params={}): def get_tool(self, tool_id, params={}): """ + V1 only Retrieve a specific tool by ID `Args:` tool_id: str @@ -154,8 +164,9 @@ def get_tool(self, tool_id, params={}): ) return response - def lookup_targets(self, tool_id, search=None, params={}): + def lookup_targets(self, target_id, search=None, location=None, params={}): """ + V1 only Lookup targets for a given tool `Args:` tool_id: str @@ -167,9 +178,11 @@ def lookup_targets(self, tool_id, search=None, params={}): `Returns:` Parsons Table containing target data. """ - endpoint = f"lookup/{tool_id}" + endpoint = f"lookup/{target_id}" if search: endpoint += f"/{search}" + if location: + endpoint += f"/{location}" response = self.converted_request( endpoint=endpoint, method="GET", params=params ) @@ -177,6 +190,7 @@ def lookup_targets(self, tool_id, search=None, params={}): def get_action(self, tool_id, params={}): """ + V1 only Get action information for a specific tool `Args:` tool_id: str @@ -193,6 +207,7 @@ def get_action(self, tool_id, params={}): def run_action(self, tool_id, payload, params={}): """ + V1 only Run a specific action for a tool `Args:` tool_id: str @@ -208,40 +223,34 @@ def run_action(self, tool_id, payload, params={}): endpoint=f"action/{tool_id}", method="PATCH", payload=payload, params=params ) return response - - def get_target(self, target_id, params={}): - """ - Retrieve a specific target by ID - `Args:` - target_id: str - The ID of the target to retrieve. - params: dict - Query parameters to include in the request. - `Returns:` - Parsons Table containing target data. - """ - response = self.converted_request( - endpoint=f"target/{target_id}", method="GET", params=params - ) - return response - + def get_campaigns(self, params={}): """ + V1 & V2 Retrieve all campaigns + In v2, a campaign is equivalent to Tools or Actions in V1. `Args:` params: dict Query parameters to include in the request. `Returns:` Parsons Table containing campaigns data. """ + if "v1" in self.api_version: + endpoint = "campaign" + else: + self.api_version = "jsonapi" + endpoint = "action/action" response = self.converted_request( - endpoint="campaign", method="GET", params=params + endpoint=endpoint, method="GET", params=params ) return response def get_campaign(self, campaign_id, params={}): """ - Retrieve a specific campaign by ID + V1 & V2 + Retrieve a specific campaign by ID. + + In v2, a campaign is equivalent to Tools or Actions in V1. `Args:` campaign_id: str The ID of the campaign to retrieve. @@ -250,13 +259,16 @@ def get_campaign(self, campaign_id, params={}): `Returns:` Parsons Table containing campaign data. """ + endpoint = f"campaign/{campaign_id}" if "v1" in self.api_version else f"/campaign/{campaign_id}/form" + response = self.converted_request( - endpoint=f"campaign/{campaign_id}", method="GET", params=params + endpoint=endpoint, method="GET", params=params ) return response def get_organizations(self, params={}): """ + V1 only Retrieve all organizations `Args:` params: dict @@ -271,6 +283,7 @@ def get_organizations(self, params={}): def get_organization(self, organization_id, params={}): """ + V1 only Retrieve a specific organization by ID `Args:` organization_id: str @@ -287,6 +300,7 @@ def get_organization(self, organization_id, params={}): def get_services(self, params={}): """ + V1 only Retrieve all services `Args:` params: dict @@ -301,6 +315,7 @@ def get_services(self, params={}): def get_service(self, service_id, params={}): """ + V1 only Retrieve a specific service by ID `Args:` service_id: str @@ -315,8 +330,24 @@ def get_service(self, service_id, params={}): ) return response + def get_target(self, target_id, params={}): + """ + V1 only + Get specific target. + `Args:` + params: dict + Query parameters to include in the request. + `Returns:` + Parsons Table containing targets data. + """ + response = self.converted_request( + endpoint=f"target/{target_id}", method="GET", params=params + ) + return response + def get_targets(self, params={}): """ + V1 only Retrieve all targets `Args:` params: dict @@ -331,6 +362,7 @@ def get_targets(self, params={}): def get_outreaches(self, tool_id, params={}): """ + V1 only Retrieve all outreaches for a specific tool `Args:` tool_id: str @@ -348,6 +380,7 @@ def get_outreaches(self, tool_id, params={}): def get_outreach(self, outreach_id, params={}): """ + V1 only Retrieve a specific outreach by ID `Args:` outreach_id: str @@ -361,3 +394,61 @@ def get_outreach(self, outreach_id, params={}): endpoint=f"outreach/{outreach_id}", method="GET", params=params ) return response + + def get_recipient(self, campaign_id, params={}): + """ + V2 only + Retrieve a specific recipient by ID + `Args:` + campaign_id: str + The ID of the campaign to retrieve. + params: dict + Query parameters to include in the request. + `Returns:` + Parsons Table containing recipient data. + """ + response = self.converted_request( + endpoint=f"campaign/{campaign_id}/target", method="GET", params=params + ) + return response + + def run_submit(self, campaign_id, params={}): + """ + V2 only + Pass a submission from a supporter to a campaign + that ultimately fills in a petition, + sends an email or triggers a phone call + depending on your campaign type + + `Args:` + campaign_id: str + The ID of the campaign to retrieve. + params: dict + Query parameters to include in the request. + `Returns:` + Parsons Table containing submit data. + """ + response = self.converted_request( + endpoint=f"campaign/{campaign_id}/submit ", method="POST", params=params + ) + return response + + def get_submissions(self, params={}): + """ + V2 only + Retrieve and sort submission and contact data + for your organization using a range of filters + that include campaign id, data range and submission status + + `Args:` + campaign_id: str + The ID of the campaign to retrieve. + params: dict + Query parameters to include in the request. + `Returns:` + Parsons Table containing submit data. + """ + response = self.converted_request( + endpoint="submission", method="POST", params=params + ) + return response \ No newline at end of file diff --git a/parsons/utilities/oauth_api_connector.py b/parsons/utilities/oauth_api_connector.py index d0b006f5b9..8ea4675168 100644 --- a/parsons/utilities/oauth_api_connector.py +++ b/parsons/utilities/oauth_api_connector.py @@ -41,7 +41,7 @@ def __init__( client_id: str, client_secret: str, token_url: str, - auto_refresh_url: Optional[str], + auto_refresh_url: str, headers: Optional[Dict[str, str]] = None, pagination_key: Optional[str] = None, data_key: Optional[str] = None, From b0d339533bc0cdf0d8c7a2ff3b36d687965ad2f6 Mon Sep 17 00:00:00 2001 From: sharinetmc Date: Fri, 13 Dec 2024 07:59:18 -1000 Subject: [PATCH 07/10] more changes --- parsons/newmode/newmode.py | 122 ++++++++++++++++++++++++++++--------- 1 file changed, 94 insertions(+), 28 deletions(-) diff --git a/parsons/newmode/newmode.py b/parsons/newmode/newmode.py index 552221b7d6..4501ba2b9d 100644 --- a/parsons/newmode/newmode.py +++ b/parsons/newmode/newmode.py @@ -9,7 +9,8 @@ API_URL_V1 = "https://engage.newmode.net/api/" API_URL_V2 = "https://base.newmode.net/api/" -API_AUTH_URL = 'https://base.newmode.net/oauth/token' +API_AUTH_URL = "https://base.newmode.net/oauth/token" + class Newmode(object): """ @@ -27,7 +28,14 @@ class Newmode(object): NewMode Class """ - def __init__(self, api_user=None, api_password=None, client_id=None, client_secret=None, api_version="v1.0"): + def __init__( + self, + api_user=None, + api_password=None, + client_id=None, + client_secret=None, + api_version="v1.0", + ): self.api_version = check_env.check("NEWMODE_API_VERSION", api_version) if "v1" in self.api_version: @@ -41,11 +49,14 @@ def __init__(self, api_user=None, api_password=None, client_id=None, client_secr self.client = APIConnector( self.base_url, auth=(self.api_user, self.api_password), - headers=self.headers,) + headers=self.headers, + ) else: self.base_url = API_URL_V2 self.client_id = check_env.check("NEWMODE_API_CLIENT_ID", client_id) - self.__client_secret = check_env.check("NEWMODE_API_CLIENT_SECRET", client_secret) + self.__client_secret = check_env.check( + "NEWMODE_API_CLIENT_SECRET", client_secret + ) self.headers = {"content-type": "application/x-www-form-urlencoded"} self.client = OAuth2APIConnector( uri=self.base_url, @@ -54,7 +65,8 @@ def __init__(self, api_user=None, api_password=None, client_id=None, client_secr client_secret=self.__client_secret, headers=self.headers, token_url=API_AUTH_URL, - grant_type="client_credentials",) + grant_type="client_credentials", + ) def convert_to_table(self, data): """Internal method to create a Parsons table from a data element.""" @@ -67,7 +79,9 @@ def convert_to_table(self, data): return table - def base_request(self, method, url, requires_csrf=True, params={}): + def base_request( + self, method, url, requires_csrf=True, data=None, json=None, params={} + ): if requires_csrf: csrf = self.get_csrf_token() @@ -76,11 +90,10 @@ def base_request(self, method, url, requires_csrf=True, params={}): response = None if method == "GET": response = self.client.get_request(url=url, params=params) - # if "targets" in url: - # response = self.client.get_request(url=url, params=params)['_embedded']['hal:tool'] elif method == "PATCH": response = self.client.patch_request(url=url, params=params) - # response.get("_embedded", {}).get(f"osdi:{object_name}") + elif method == "POST": + response = self.client.post_request(url=url, params=params, json=json) return response def converted_request( @@ -89,12 +102,19 @@ def converted_request( method, requires_csrf=True, supports_version=True, + data=None, + json=None, params={}, convert_to_table=True, ): url = f"{self.api_version}/{endpoint}" if supports_version else endpoint response = self.base_request( - method=method, url=url, requires_csrf=requires_csrf, params=params + method=method, + url=url, + requires_csrf=requires_csrf, + json=json, + data=data, + params=params, ) if not response: logging.warning(f"Empty result returned from endpoint: {endpoint}") @@ -102,7 +122,7 @@ def converted_request( return self.convert_to_table(response) else: return response - + def get_csrf_token(self, max_retries=10): """ Retrieve a CSRF token for making API requests @@ -223,7 +243,7 @@ def run_action(self, tool_id, payload, params={}): endpoint=f"action/{tool_id}", method="PATCH", payload=payload, params=params ) return response - + def get_campaigns(self, params={}): """ V1 & V2 @@ -237,11 +257,13 @@ def get_campaigns(self, params={}): """ if "v1" in self.api_version: endpoint = "campaign" + requires_csrf = True else: self.api_version = "jsonapi" endpoint = "action/action" + requires_csrf = False response = self.converted_request( - endpoint=endpoint, method="GET", params=params + endpoint=endpoint, method="GET", params=params, requires_csrf=requires_csrf ) return response @@ -259,10 +281,14 @@ def get_campaign(self, campaign_id, params={}): `Returns:` Parsons Table containing campaign data. """ - endpoint = f"campaign/{campaign_id}" if "v1" in self.api_version else f"/campaign/{campaign_id}/form" - + if "v1" in self.api_version: + endpoint = f"campaign/{campaign_id}" + requires_csrf = True + else: + endpoint = f"/campaign/{campaign_id}/form" + requires_csrf = False response = self.converted_request( - endpoint=endpoint, method="GET", params=params + endpoint=endpoint, method="GET", params=params, requires_csrf=requires_csrf ) return response @@ -395,29 +421,65 @@ def get_outreach(self, outreach_id, params={}): ) return response - def get_recipient(self, campaign_id, params={}): + def get_recipient( + self, + campaign_id, + street_address=None, + city=None, + postal_code=None, + region=None, + params={}, + ): """ V2 only Retrieve a specific recipient by ID `Args:` campaign_id: str The ID of the campaign to retrieve. + street_address: str + Street address of recipient + city: str + City of recipient + postal_code: str + Postal code of recipient + region: str + Region (i.e. state/province abbreviation) of recipient params: dict Query parameters to include in the request. `Returns:` Parsons Table containing recipient data. """ + address_params = { + "street_address": street_address, + "city": city, + "postal_code": postal_code, + "region": region, + } + if all(x is None for x in address_params.values()): + logger.error( + "Please specify a street address, city, postal code, and/or region." + ) + raise Exception("Incomplete Request") + + params = { + f"address[value][{key}]": value + for key, value in address_params.items() + if value + } response = self.converted_request( - endpoint=f"campaign/{campaign_id}/target", method="GET", params=params + endpoint=f"campaign/{campaign_id}/target", + method="GET", + params=params, + requires_csrf=False, ) return response - def run_submit(self, campaign_id, params={}): + def run_submit(self, campaign_id, json=None, data=None, params={}): """ V2 only Pass a submission from a supporter to a campaign - that ultimately fills in a petition, - sends an email or triggers a phone call + that ultimately fills in a petition, + sends an email or triggers a phone call depending on your campaign type `Args:` @@ -429,26 +491,30 @@ def run_submit(self, campaign_id, params={}): Parsons Table containing submit data. """ response = self.converted_request( - endpoint=f"campaign/{campaign_id}/submit ", method="POST", params=params + endpoint=f"campaign/{campaign_id}/submit", + method="POST", + data=data, + json=json, + params=params, + requires_csrf=False, + convert_to_table=False, ) return response def get_submissions(self, params={}): """ V2 only - Retrieve and sort submission and contact data - for your organization using a range of filters + Retrieve and sort submission and contact data + for your organization using a range of filters that include campaign id, data range and submission status `Args:` - campaign_id: str - The ID of the campaign to retrieve. params: dict Query parameters to include in the request. `Returns:` Parsons Table containing submit data. """ response = self.converted_request( - endpoint="submission", method="POST", params=params + endpoint="submission", method="GET", params=params, requires_csrf=False ) - return response \ No newline at end of file + return response From b813a226e0bf8d8c893542428a89a742dc58cee1 Mon Sep 17 00:00:00 2001 From: sharinetmc Date: Thu, 19 Dec 2024 16:06:06 -0800 Subject: [PATCH 08/10] edit methods --- parsons/newmode/newmode.py | 239 +++++++++++++++++++++++++++---------- 1 file changed, 179 insertions(+), 60 deletions(-) diff --git a/parsons/newmode/newmode.py b/parsons/newmode/newmode.py index 4501ba2b9d..2d5072729d 100644 --- a/parsons/newmode/newmode.py +++ b/parsons/newmode/newmode.py @@ -9,7 +9,8 @@ API_URL_V1 = "https://engage.newmode.net/api/" API_URL_V2 = "https://base.newmode.net/api/" -API_AUTH_URL = "https://base.newmode.net/oauth/token" +API_AUTH_URL = "https://base.newmode.net/oauth/token/" +API_CAMPAIGNS_V2_URL = "https://base.newmode.net/" class Newmode(object): @@ -37,7 +38,7 @@ def __init__( api_version="v1.0", ): self.api_version = check_env.check("NEWMODE_API_VERSION", api_version) - + # TODO: delete when v1 depreciated if "v1" in self.api_version: logger.warning( "Newmode API v1 will no longer be supported starting Feburary 2025." @@ -58,15 +59,6 @@ def __init__( "NEWMODE_API_CLIENT_SECRET", client_secret ) self.headers = {"content-type": "application/x-www-form-urlencoded"} - self.client = OAuth2APIConnector( - uri=self.base_url, - auto_refresh_url=API_AUTH_URL, - client_id=self.client_id, - client_secret=self.__client_secret, - headers=self.headers, - token_url=API_AUTH_URL, - grant_type="client_credentials", - ) def convert_to_table(self, data): """Internal method to create a Parsons table from a data element.""" @@ -79,10 +71,10 @@ def convert_to_table(self, data): return table + # TODO: delete when v1 deprecated def base_request( self, method, url, requires_csrf=True, data=None, json=None, params={} ): - if requires_csrf: csrf = self.get_csrf_token() self.headers["X-CSRF-Token"] = csrf @@ -93,9 +85,24 @@ def base_request( elif method == "PATCH": response = self.client.patch_request(url=url, params=params) elif method == "POST": - response = self.client.post_request(url=url, params=params, json=json) + response = self.client.post_request( + url=url, params=params, json=json, data=data + ) return response + def base_request_v2(self, method, url, data=None, json=None, params={}): + response = None + if method == "GET": + response = self.client.get_request(url=url, params=params) + elif method == "PATCH": + response = self.client.patch_request(url=url, params=params) + elif method == "POST": + response = self.client.post_request( + url=url, params=params, json=json, data=data + ) + return response + + # TODO: delete when v1 deprecated def converted_request( self, endpoint, @@ -123,6 +130,42 @@ def converted_request( else: return response + def converted_request_v2( + self, + endpoint, + method, + supports_version=True, + data=None, + json=None, + params={}, + convert_to_table=True, + ): + self.client = OAuth2APIConnector( + uri=self.base_url, + auto_refresh_url=API_AUTH_URL, + client_id=self.client_id, + client_secret=self.__client_secret, + headers=self.headers, + token_url=API_AUTH_URL, + grant_type="client_credentials", + ) + + url = f"{self.api_version}/{endpoint}" if supports_version else endpoint + response = self.base_request_v2( + method=method, + url=url, + json=json, + data=data, + params=params, + ) + if not response: + logging.warning(f"Empty result returned from endpoint: {endpoint}") + if convert_to_table: + return self.convert_to_table(response) + else: + return response + + # TODO:delete when v1 deprecated def get_csrf_token(self, max_retries=10): """ Retrieve a CSRF token for making API requests @@ -154,6 +197,37 @@ def get_csrf_token(self, max_retries=10): ) time.sleep(attempt + 1) + # def get_csrf_token_v2(self, max_retries=10): + # """ + # Retrieve a CSRF token for making API requests + # `Args:` + # max_retries: int + # The maximum number of attempts to get the CSRF token. + # `Returns:` + # The CSRF token. + # """ + # endpoint = "/user/login?_format=json" + # for attempt in range(max_retries): + # # try: + # response = self.converted_request_v2( + # endpoint=endpoint, + # method="POST", + # supports_version=False, + # convert_to_table=False, + # ) + # print(response) + # return response + # except Exception as e: + # if attempt >= max_retries: + # logger.error( + # (f"Error getting CSRF Token after {max_retries} retries") + # ) + # raise e + # logger.warning( + # f"Retry {attempt} at getting CSRF Token failed. Retrying. Error: {e}" + # ) + # time.sleep(attempt + 1) + def get_tools(self, params={}): """ V1 only @@ -246,22 +320,16 @@ def run_action(self, tool_id, payload, params={}): def get_campaigns(self, params={}): """ - V1 & V2 + V1 Retrieve all campaigns - In v2, a campaign is equivalent to Tools or Actions in V1. `Args:` params: dict Query parameters to include in the request. `Returns:` Parsons Table containing campaigns data. """ - if "v1" in self.api_version: - endpoint = "campaign" - requires_csrf = True - else: - self.api_version = "jsonapi" - endpoint = "action/action" - requires_csrf = False + endpoint = "campaign" + requires_csrf = True response = self.converted_request( endpoint=endpoint, method="GET", params=params, requires_csrf=requires_csrf ) @@ -269,10 +337,9 @@ def get_campaigns(self, params={}): def get_campaign(self, campaign_id, params={}): """ - V1 & V2 + V1 Retrieve a specific campaign by ID. - In v2, a campaign is equivalent to Tools or Actions in V1. `Args:` campaign_id: str The ID of the campaign to retrieve. @@ -281,12 +348,8 @@ def get_campaign(self, campaign_id, params={}): `Returns:` Parsons Table containing campaign data. """ - if "v1" in self.api_version: - endpoint = f"campaign/{campaign_id}" - requires_csrf = True - else: - endpoint = f"/campaign/{campaign_id}/form" - requires_csrf = False + endpoint = f"campaign/{campaign_id}" + requires_csrf = True response = self.converted_request( endpoint=endpoint, method="GET", params=params, requires_csrf=requires_csrf ) @@ -421,6 +484,56 @@ def get_outreach(self, outreach_id, params={}): ) return response + def get_campaign_v2(self, campaign_id, params={}): + """ + V2 + Retrieve a specific campaign by ID. + + In v2, a campaign is equivalent to Tools or Actions in V1. + `Args:` + campaign_id: str + The ID of the campaign to retrieve. + params: dict + Query parameters to include in the request. + `Returns:` + Parsons Table containing campaign data. + """ + endpoint = f"/campaign/{campaign_id}/form" + response = self.converted_request_v2( + endpoint=endpoint, + method="GET", + params=params, + ) + return response + + def get_campaigns_v2(self, organization_id, params={}): + """ + V2 + Retrieve all campaigns + In v2, a campaign is equivalent to Tools or Actions in V1. + `Args:` + organization_id: str + ID of organization + params: dict + Query parameters to include in the request. + `Returns:` + Parsons Table containing campaigns data. + """ + self.base_url = API_CAMPAIGNS_V2_URL + self.api_version = "jsonapi" + self.headers = { + "content-type": "application/vnd.api+json", + "accept": "application/vnd.api+json", + "authorization": "Bearer 1234567890", + } + endpoint = f"action/action?filter[field_org_id]={organization_id}" + response = self.converted_request_v2( + endpoint=endpoint, + method="GET", + params=params, + ) + return response + def get_recipient( self, campaign_id, @@ -466,40 +579,42 @@ def get_recipient( for key, value in address_params.items() if value } - response = self.converted_request( + response = self.converted_request_v2( endpoint=f"campaign/{campaign_id}/target", method="GET", params=params, - requires_csrf=False, ) return response - def run_submit(self, campaign_id, json=None, data=None, params={}): - """ - V2 only - Pass a submission from a supporter to a campaign - that ultimately fills in a petition, - sends an email or triggers a phone call - depending on your campaign type - - `Args:` - campaign_id: str - The ID of the campaign to retrieve. - params: dict - Query parameters to include in the request. - `Returns:` - Parsons Table containing submit data. - """ - response = self.converted_request( - endpoint=f"campaign/{campaign_id}/submit", - method="POST", - data=data, - json=json, - params=params, - requires_csrf=False, - convert_to_table=False, - ) - return response + # TODO: add run_submit method + # def run_submit(self, campaign_id, json=None, data=None, params={}): + # """ + # V2 only + # Pass a submission from a supporter to a campaign + # that ultimately fills in a petition, + # sends an email or triggers a phone call + # depending on your campaign type + + # `Args:` + # campaign_id: str + # The ID of the campaign to retrieve. + # params: dict + # Query parameters to include in the request. + # `Returns:` + # Parsons Table containing submit data. + # """ + # # json["action_id"] = campaign_id + # self.api_version = 2.0 + # response = self.converted_request( + # endpoint=f"action/{campaign_id}/submit/?_format=json", + # method="POST", + # data=data, + # json=json, + # params=params, + # requires_csrf=False, + # convert_to_table=False, + # ) + # return response def get_submissions(self, params={}): """ @@ -514,7 +629,11 @@ def get_submissions(self, params={}): `Returns:` Parsons Table containing submit data. """ - response = self.converted_request( - endpoint="submission", method="GET", params=params, requires_csrf=False + # self.base_url = API_AUTH_URL + # print(self.base_url) + # response=self.converted_request_v2(endpoint="/user/login?_format=json", method="POST", supports_version=False) + # self.token = self.token['access_token'] + response = self.converted_request_v2( + endpoint="submission", method="GET", params=params ) return response From 580d4ca89dcc2728f027149c380621a85e3a0259 Mon Sep 17 00:00:00 2001 From: sharinetmc Date: Thu, 19 Dec 2024 16:08:54 -0800 Subject: [PATCH 09/10] ruff format --- parsons/newmode/newmode.py | 86 ++++++++++++-------------------------- 1 file changed, 27 insertions(+), 59 deletions(-) diff --git a/parsons/newmode/newmode.py b/parsons/newmode/newmode.py index 2d5072729d..62ce32d783 100644 --- a/parsons/newmode/newmode.py +++ b/parsons/newmode/newmode.py @@ -40,9 +40,7 @@ def __init__( self.api_version = check_env.check("NEWMODE_API_VERSION", api_version) # TODO: delete when v1 depreciated if "v1" in self.api_version: - logger.warning( - "Newmode API v1 will no longer be supported starting Feburary 2025." - ) + logger.warning("Newmode API v1 will no longer be supported starting Feburary 2025.") self.base_url = API_URL_V1 self.api_user = check_env.check("NEWMODE_API_USER", api_user) self.api_password = check_env.check("NEWMODE_API_PASSWORD", api_password) @@ -55,9 +53,7 @@ def __init__( else: self.base_url = API_URL_V2 self.client_id = check_env.check("NEWMODE_API_CLIENT_ID", client_id) - self.__client_secret = check_env.check( - "NEWMODE_API_CLIENT_SECRET", client_secret - ) + self.__client_secret = check_env.check("NEWMODE_API_CLIENT_SECRET", client_secret) self.headers = {"content-type": "application/x-www-form-urlencoded"} def convert_to_table(self, data): @@ -72,9 +68,7 @@ def convert_to_table(self, data): return table # TODO: delete when v1 deprecated - def base_request( - self, method, url, requires_csrf=True, data=None, json=None, params={} - ): + def base_request(self, method, url, requires_csrf=True, data=None, json=None, params={}): if requires_csrf: csrf = self.get_csrf_token() self.headers["X-CSRF-Token"] = csrf @@ -85,9 +79,7 @@ def base_request( elif method == "PATCH": response = self.client.patch_request(url=url, params=params) elif method == "POST": - response = self.client.post_request( - url=url, params=params, json=json, data=data - ) + response = self.client.post_request(url=url, params=params, json=json, data=data) return response def base_request_v2(self, method, url, data=None, json=None, params={}): @@ -97,9 +89,7 @@ def base_request_v2(self, method, url, data=None, json=None, params={}): elif method == "PATCH": response = self.client.patch_request(url=url, params=params) elif method == "POST": - response = self.client.post_request( - url=url, params=params, json=json, data=data - ) + response = self.client.post_request(url=url, params=params, json=json, data=data) return response # TODO: delete when v1 deprecated @@ -149,7 +139,7 @@ def converted_request_v2( token_url=API_AUTH_URL, grant_type="client_credentials", ) - + url = f"{self.api_version}/{endpoint}" if supports_version else endpoint response = self.base_request_v2( method=method, @@ -188,9 +178,7 @@ def get_csrf_token(self, max_retries=10): return response["X-CSRF-Token"] except Exception as e: if attempt >= max_retries: - logger.error( - (f"Error getting CSRF Token after {max_retries} retries") - ) + logger.error((f"Error getting CSRF Token after {max_retries} retries")) raise e logger.warning( f"Retry {attempt} at getting CSRF Token failed. Retrying. Error: {e}" @@ -216,17 +204,17 @@ def get_csrf_token(self, max_retries=10): # convert_to_table=False, # ) # print(response) - # return response - # except Exception as e: - # if attempt >= max_retries: - # logger.error( - # (f"Error getting CSRF Token after {max_retries} retries") - # ) - # raise e - # logger.warning( - # f"Retry {attempt} at getting CSRF Token failed. Retrying. Error: {e}" - # ) - # time.sleep(attempt + 1) + # return response + # except Exception as e: + # if attempt >= max_retries: + # logger.error( + # (f"Error getting CSRF Token after {max_retries} retries") + # ) + # raise e + # logger.warning( + # f"Retry {attempt} at getting CSRF Token failed. Retrying. Error: {e}" + # ) + # time.sleep(attempt + 1) def get_tools(self, params={}): """ @@ -253,9 +241,7 @@ def get_tool(self, tool_id, params={}): `Returns:` Parsons Table containing the tool data. """ - response = self.converted_request( - endpoint=f"tool/{tool_id}", method="GET", params=params - ) + response = self.converted_request(endpoint=f"tool/{tool_id}", method="GET", params=params) return response def lookup_targets(self, target_id, search=None, location=None, params={}): @@ -277,9 +263,7 @@ def lookup_targets(self, target_id, search=None, location=None, params={}): endpoint += f"/{search}" if location: endpoint += f"/{location}" - response = self.converted_request( - endpoint=endpoint, method="GET", params=params - ) + response = self.converted_request(endpoint=endpoint, method="GET", params=params) return response def get_action(self, tool_id, params={}): @@ -294,9 +278,7 @@ def get_action(self, tool_id, params={}): `Returns:` Parsons Table containing action data. """ - response = self.converted_request( - endpoint=f"action/{tool_id}", method="GET", params=params - ) + response = self.converted_request(endpoint=f"action/{tool_id}", method="GET", params=params) return response def run_action(self, tool_id, payload, params={}): @@ -365,9 +347,7 @@ def get_organizations(self, params={}): `Returns:` Parsons Table containing organizations data. """ - response = self.converted_request( - endpoint="organization", method="GET", params=params - ) + response = self.converted_request(endpoint="organization", method="GET", params=params) return response def get_organization(self, organization_id, params={}): @@ -397,9 +377,7 @@ def get_services(self, params={}): `Returns:` Parsons Table containing services data. """ - response = self.converted_request( - endpoint="service", method="GET", params=params - ) + response = self.converted_request(endpoint="service", method="GET", params=params) return response def get_service(self, service_id, params={}): @@ -444,9 +422,7 @@ def get_targets(self, params={}): `Returns:` Parsons Table containing targets data. """ - response = self.converted_request( - endpoint="target", method="GET", params=params - ) + response = self.converted_request(endpoint="target", method="GET", params=params) return response def get_outreaches(self, tool_id, params={}): @@ -569,16 +545,10 @@ def get_recipient( "region": region, } if all(x is None for x in address_params.values()): - logger.error( - "Please specify a street address, city, postal code, and/or region." - ) + logger.error("Please specify a street address, city, postal code, and/or region.") raise Exception("Incomplete Request") - params = { - f"address[value][{key}]": value - for key, value in address_params.items() - if value - } + params = {f"address[value][{key}]": value for key, value in address_params.items() if value} response = self.converted_request_v2( endpoint=f"campaign/{campaign_id}/target", method="GET", @@ -633,7 +603,5 @@ def get_submissions(self, params={}): # print(self.base_url) # response=self.converted_request_v2(endpoint="/user/login?_format=json", method="POST", supports_version=False) # self.token = self.token['access_token'] - response = self.converted_request_v2( - endpoint="submission", method="GET", params=params - ) + response = self.converted_request_v2(endpoint="submission", method="GET", params=params) return response From 77bb8b34b21cdb75973fe9b1f16bf99672e9d95c Mon Sep 17 00:00:00 2001 From: sharinetmc Date: Thu, 19 Dec 2024 18:53:17 -0800 Subject: [PATCH 10/10] debug tests --- parsons/newmode/newmode.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/parsons/newmode/newmode.py b/parsons/newmode/newmode.py index 62ce32d783..1b613792f2 100644 --- a/parsons/newmode/newmode.py +++ b/parsons/newmode/newmode.py @@ -296,7 +296,7 @@ def run_action(self, tool_id, payload, params={}): Parsons Table containing posted outreach information. """ response = self.converted_request( - endpoint=f"action/{tool_id}", method="PATCH", payload=payload, params=params + endpoint=f"action/{tool_id}", method="PATCH", data=payload, params=params ) return response