From 9f641c2bbf88f9dcf22478ae6424b60919159f6d Mon Sep 17 00:00:00 2001 From: nolan Date: Sat, 2 Dec 2023 20:20:00 -0800 Subject: [PATCH] initial commit --- .gitignore | 23 +++ README.md | 1 + lib_afc_mosiac/__init__.py | 10 ++ lib_afc_mosiac/afcbusiness.py | 210 ++++++++++++++++++++++++++++ lib_afc_mosiac/afs3exception.py | 6 + lib_afc_mosiac/universe_map_file.py | 122 ++++++++++++++++ setup.py | 25 ++++ 7 files changed, 397 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100755 lib_afc_mosiac/__init__.py create mode 100644 lib_afc_mosiac/afcbusiness.py create mode 100644 lib_afc_mosiac/afs3exception.py create mode 100644 lib_afc_mosiac/universe_map_file.py create mode 100644 setup.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2c078a1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,23 @@ +venv/ +test-venv/ + +*.pyc +__pycache__/ + +instance/ + +.pytest_cache/ +.coverage +htmlcov/ + +dist/ +build/ +*.egg-info/ + +node_modules/ + +.envs +session_files/ +mongo_data/ +test_data/ + diff --git a/README.md b/README.md new file mode 100644 index 0000000..f9e4fb8 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +Library AF Mosiac diff --git a/lib_afc_mosiac/__init__.py b/lib_afc_mosiac/__init__.py new file mode 100755 index 0000000..9e0fb7c --- /dev/null +++ b/lib_afc_mosiac/__init__.py @@ -0,0 +1,10 @@ +""" +lib_afc_mosiac +--------------------------------------- + +A Python library for working with AFC Mosiac files + +""" + +from lib_afc_mosiac.universe_map_file import AFCM_UniverseMapFile +#from lib_afc_api.afccampaign import AFCCampaign diff --git a/lib_afc_mosiac/afcbusiness.py b/lib_afc_mosiac/afcbusiness.py new file mode 100644 index 0000000..750a7f3 --- /dev/null +++ b/lib_afc_mosiac/afcbusiness.py @@ -0,0 +1,210 @@ +import sys +import base64 +import requests +import datetime +import json + +# added this to silence annoying SSL warnings in requests +import urllib3 +urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) + +class AFCBusiness: + def __init__( self, api_url=None, api_jwt=None, username=None, password=None ): + # Update this as we modify the library for newer versions + self.api_version = 'v1' + + self.api_jwt = api_jwt + #self.api_url = f"https://afaccount.roosevelt.hyperscaleaf.com/api/{self.api_version}/businesses" + + # Dev + self.api_url = f"https://afaccount.eisenhower.hyperscaleaf.com/api/" + + # Prod + #self.api_url = f"https://afaccount.roosevelt.hyperscaleaf.com/api/" + + self.credentials = {} + self.credentials['user_name'] = username #'rross' + self.credentials['password'] = password #'rGstj!f672q5FBZ' + + self.headers = {} + self.headers['content-type'] = "application/json" + self.headers['accept'] = "application/json" + + if self.api_jwt == None: + if self.credentials['user_name'] and self.credentials['password']: + self.Set_JWT() + else: + print("Unable to connect to API without jwt or authentication credentials") + sys.exit(1) + + self.headers['authorization'] = "Bearer " + self.api_jwt + + self.params = {} + + def Set_JWT( self ): + url = f"{self.api_url}{self.api_version}/auth/authenticate" + + try: + response = requests.post(url, headers=self.headers, json=self.credentials) + except requests.exceptions.RequestException as e: + print("exception: {}".format(e)) + sys.exit(1) + + try: + response.raise_for_status() + except requests.exceptions.HTTPError as err: + print("http error: {}".format(err)) + sys.exit(1) + + rj = response.json(); + self.api_jwt = rj['jwt'] + + return + + def Submit_Get( self, full_url, input_params={} ): + if not isinstance(full_url, str): + print("Submit_Get() error: must provide the full url string") + return None + + try: + self.response = requests.get(full_url, verify=False, headers=self.headers, params=input_params) + #print("debug: url = {}".format(self.response.url)) + except requests.exceptions.RequestException as e: + print("exception: {}".format(e)) + #sys.exit(1) + return None + + try: + self.response.raise_for_status() + except requests.exceptions.HTTPError as err: + print("http error: {}".format(err)) + #sys.exit(1) + return None + + return self.response + + def Submit_Post( self, full_url, input_params={}, data={} ): + if not isinstance(full_url, str): + print("Submit_Post() error: must provide the full url string") + return None + + print(f"\ndata = {data}") + + try: + this_headers = self.headers + this_headers['Content-Type'] = 'application/json' + #self.response = requests.post(full_url, verify=False, headers=this_headers, params=input_params, data=data) + self.response = requests.post(full_url, verify=False, headers=this_headers, data=json.dumps(data)) + #print("debug: url = {}".format(self.response.url)) + except requests.exceptions.RequestException as e: + print("exception: {}".format(e)) + #sys.exit(1) + return None + + try: + self.response.raise_for_status() + except requests.exceptions.HTTPError as err: + print("http error: {}".format(err)) + print(f"\nself.response.attributes = {self.response.__dict__}") + #sys.exit(1) + return None + + return self.response + + def Submit_Patch( self, full_url, input_params={}, data={} ): + if not isinstance(full_url, str): + print("Submit_Patch() error: must provide the full url string") + return None + + #print(f"\ndata = {data}") + + try: + this_headers = self.headers + this_headers['Content-Type'] = 'application/json' + #self.response = requests.post(full_url, verify=False, headers=this_headers, params=input_params, data=data) + self.response = requests.patch(full_url, verify=False, headers=this_headers, data=json.dumps(data)) + #print("debug: url = {}".format(self.response.url)) + except requests.exceptions.RequestException as e: + print("exception: {}".format(e)) + #sys.exit(1) + return None + + try: + self.response.raise_for_status() + except requests.exceptions.HTTPError as err: + print("http error: {}".format(err)) + print(f"\nself.response.attributes = {self.response.__dict__}") + #sys.exit(1) + return None + + return self.response + + def GetBusinesses(self): + business_url = f"{self.api_url}{self.api_version}/businesses" + input_params = {} + + # offset number in list + #input_params['start'] = 0 + + # max number of businesses to return in list + input_params['limit'] = 200 + + # filter to these status values + #input_params['status'] = ['active','archived'] + + r = self.Submit_Get(business_url, input_params) + results = r.json() + + if 'values' in results: + # print(f"AFC Business List") + # for b in results['values']: + # print(f"{b}\n") + return results['values'] + + return None + + def PostBusiness(self, business=None): + business_url = f"{self.api_url}{self.api_version}/businesses" + input_params = {} + + # business_object = { + # 'name' : 'string', + # "account_id_legacy": "string", + # "primary_contact_name": "string", + # "primary_contact_phone": "string", + # "primary_contact_email": "string", + # "company_public_name": "string", + # "logo_url": "string", + # "bandwidth_campaign_id": "string", + # "reports_required": true, + # "sms_base_price_contract": 0, + # "mms_base_price_contract": 0, + # "mms_included_characters": 0, + # "afc_connect_base_price_contract": 0, + # "afc_crystal_pricing": 0 + # } + + r = self.Submit_Post(business_url, input_params, data=business) + if r == None: + return None + + results = r.json() + return results + + def PatchBusiness(self, business_key=None, fields=None): + if business_key == None: + print(f"Can't patch a business without the business key") + return None + + if fields == None: + print(f"Can't patch a business without a dict of fields") + return None + + business_url = f"{self.api_url}{self.api_version}/businesses/{business_key}" + + r = self.Submit_Patch(business_url, input_params={}, data=fields) + if r == None: + return None + + results = r.json() + return results diff --git a/lib_afc_mosiac/afs3exception.py b/lib_afc_mosiac/afs3exception.py new file mode 100644 index 0000000..fd974a0 --- /dev/null +++ b/lib_afc_mosiac/afs3exception.py @@ -0,0 +1,6 @@ +""" +AF Mosiac exceptions +""" + +class AFCMUniverseMapFileError(Exception): + """AFCMUniverseMapFileError class. Custom Exception object.""" diff --git a/lib_afc_mosiac/universe_map_file.py b/lib_afc_mosiac/universe_map_file.py new file mode 100644 index 0000000..8bafdbc --- /dev/null +++ b/lib_afc_mosiac/universe_map_file.py @@ -0,0 +1,122 @@ +""" +AFCMUniverseMapFile Class object +""" + +import json + + +class AFCMUniverseMapFile: + """ + AFCMUniverseMapFile class + """ + + def __init__( self, mosaic_filename=None ): + """ + __init__ + + :param mosaic_filename: + """ + + self.mosaic_filename = mosaic_filename + + self.mosaic_head = { + "filetype": "universe map file", + "version": '0.10' + } + + self.metadata = { + "mosaic_filename": "TeirabDelegateTextFile_MOSIAC.csv", + "mosaic_file_url": None, + "tags": None, + "total_rows": 0, + "total_removed": 0, + "total_optout": 0, + "processing_time": 0 + } + + self.mosaic_file_list = {} + self.files = [] + + + def read_mosaic_map_file( self, filename ): + """ + read_mosaic_map_file + """ + + if filename == '' or filename is None: + return None + + with open(filename, encoding='utf-8-sig') as json_file: + data = json.load(json_file) + #print("Type:", type(data)) + + if 'mosaic' in data: + self.mosaic_head = data['mosaic'] + + if 'metadata' in data: + self.metadata = data['metadata'] + + if 'files' in data: + self.mosaic_file_list = data['files'] + + return data + + + def write_mosaic_map_file( self, filename ): + """ + write_mosaic_map_file + """ + + if filename == '' or filename is None: + return None + + output_object = json.dumps({"mosaic": self.mosaic_head, + "metadata": self.metadata, + "files": self.mosaic_file_list}, indent=4) + + with open(filename, 'w', encoding="UTF-8") as outfile: + outfile.write(output_object) + + return None + + + def create_new_input_file( self, filename=None ): + """ + create_new_input_file + """ + + mosaic_input_file = { + "filename": filename, + "details": { + "csv_get_url": None, + "csv_get_prepped_url": None, + "csv_get_removed_url": None, + "csv_put_url": None, + "mapping": { + "UID": None, + "FName": None, + "LName": None, + "Cell_Phone": None, + "Party": None, + "State": None, + "Precinct": None, + "County": None, + "URL": None + }, + "operations": { + "insert": {"field":"*", "operator":"=", "value":"*"} + } + } + } + + return mosaic_input_file + + + def add_new_input_file( self, ifo ): + """ + add_new_input_file + """ + + if ifo is None: + return + self.files.append(ifo) diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..51e34c7 --- /dev/null +++ b/setup.py @@ -0,0 +1,25 @@ +""" +Project build definition file. +""" + +from setuptools import setup, find_packages + +with open('README.md', 'r') as f: + long_description = f.read() + +setup( + name='lib_afc_mosiac', + version='0.1.0', + author='', + author_email='', + description='', + long_description=long_description, + long_description_content_type='text/markdown', + zip_safe=False, + include_package_data=False, + packages=find_packages(), + python_requires='>=3.7', + install_requires=[ + + ], +)