From e130b9475667deb71b248a8e362087adb000c63e Mon Sep 17 00:00:00 2001 From: Landon Fackrell <128620767+LandonSmarty@users.noreply.github.com> Date: Tue, 28 Nov 2023 08:41:52 -0700 Subject: [PATCH] Landon/enrichment sdk (#44) Add support for the US Enrichment API --- examples/us_enrichment_example.py | 51 ++ setup.py | 3 +- smartystreets_python_sdk/client_builder.py | 8 +- .../us_enrichment/__init__.py | 2 + .../us_enrichment/client.py | 52 ++ .../us_enrichment/lookup.py | 18 + .../us_enrichment/response.py | 530 ++++++++++++++++++ test/us_enrichment/__init__.py | 0 test/us_enrichment/client_test.py | 40 ++ test/us_enrichment/result_test.py | 50 ++ 10 files changed, 752 insertions(+), 2 deletions(-) create mode 100644 examples/us_enrichment_example.py create mode 100644 smartystreets_python_sdk/us_enrichment/__init__.py create mode 100644 smartystreets_python_sdk/us_enrichment/client.py create mode 100644 smartystreets_python_sdk/us_enrichment/lookup.py create mode 100644 smartystreets_python_sdk/us_enrichment/response.py create mode 100644 test/us_enrichment/__init__.py create mode 100644 test/us_enrichment/client_test.py create mode 100644 test/us_enrichment/result_test.py diff --git a/examples/us_enrichment_example.py b/examples/us_enrichment_example.py new file mode 100644 index 0000000..45d0ca6 --- /dev/null +++ b/examples/us_enrichment_example.py @@ -0,0 +1,51 @@ +import os + +from smartystreets_python_sdk import SharedCredentials, StaticCredentials, exceptions, ClientBuilder + + +# from smartystreets_python_sdk.us_enrichment import + +def run(): + # key = "Your SmartyStreets Key here" + # hostname = "Your Hostname here" + + # We recommend storing your secret keys in environment variables instead---it's safer! + # for client-side requests (browser/mobile), use this code: + key = os.environ['SMARTY_AUTH_WEB'] + hostname = os.environ['SMARTY_WEBSITE_DOMAIN'] + + credentials = SharedCredentials(key, hostname) + + # for server-to-server requests, use this code: + # auth_id = os.environ['SMARTY_AUTH_ID'] + # auth_token = os.environ['SMARTY_AUTH_TOKEN'] + # + # credentials = StaticCredentials(auth_id, auth_token) + + # The appropriate license values to be used for your subscriptions + # can be found on the Subscriptions page of the account dashboard. + # https://www.smartystreets.com/docs/cloud/licensing + client = ClientBuilder(credentials).with_licenses(["us-property-data-principal-cloud"]).build_us_enrichment_api_client() + # client = ClientBuilder(credentials).with_custom_header({'User-Agent': 'smartystreets (python@0.0.0)', 'Content-Type': 'application/json'}).build_us_enrichment_api_client() + # client = ClientBuilder(credentials).with_proxy('localhost:8080', 'user', 'password').build_us_street_api_client() + # Uncomment the line above to try it with a proxy instead + + smarty_key = "1682393594" + try: + results = client.send_property_principal_lookup(smarty_key) + except Exception as err: + print(err) + return + + if not results: + print("No results found. This means the Smartykey is likely not valid.") + return + + top_result = results[0] + + print("Here is your result!") + print(top_result) + + +if __name__ == "__main__": + run() diff --git a/setup.py b/setup.py index 85aa78d..4e53389 100644 --- a/setup.py +++ b/setup.py @@ -18,7 +18,8 @@ 'smartystreets_python_sdk.international_street', 'smartystreets_python_sdk.us_reverse_geo', 'smartystreets_python_sdk.us_autocomplete_pro', - 'smartystreets_python_sdk.international_autocomplete' + 'smartystreets_python_sdk.international_autocomplete', + 'smartystreets_python_sdk.us_enrichment' ], version=__version__, description='An official library to help Python developers easily access the SmartyStreets APIs', diff --git a/smartystreets_python_sdk/client_builder.py b/smartystreets_python_sdk/client_builder.py index c36b2f7..3a72485 100644 --- a/smartystreets_python_sdk/client_builder.py +++ b/smartystreets_python_sdk/client_builder.py @@ -7,6 +7,7 @@ from smartystreets_python_sdk.us_reverse_geo import Client as USReverseGeoClient from smartystreets_python_sdk.international_street import Client as InternationalStreetClient from smartystreets_python_sdk.international_autocomplete import Client as InternationalAutocompleteClient +from smartystreets_python_sdk.us_enrichment import Client as USEnrichmentClient class ClientBuilder: @@ -33,7 +34,8 @@ def __init__(self, signer): self.US_EXTRACT_API_URL = "https://us-extract.api.smarty.com" self.US_STREET_API_URL = "https://us-street.api.smarty.com/street-address" self.US_ZIP_CODE_API_URL = "https://us-zipcode.api.smarty.com/lookup" - self.US_REVERSE_GEO_API_URL = "https://us-reverse-geo.api.smarty.com/lookup" + self.US_REVERSE_GEO_API_URL = "https://us-reverse-geo.api.smarty.com/lookup" + self.US_ENRICHMENT_API_URL = "https://us-enrichment.api.smarty.com/lookup/" def retry_at_most(self, max_retries): """ @@ -152,6 +154,10 @@ def build_us_reverse_geo_api_client(self): self.ensure_url_prefix_not_null(self.US_REVERSE_GEO_API_URL) return USReverseGeoClient(self.build_sender(), self.serializer) + def build_us_enrichment_api_client(self): + self.ensure_url_prefix_not_null(self.US_ENRICHMENT_API_URL) + return USEnrichmentClient(self.build_sender(), self.serializer) + def build_sender(self): if self.http_sender is not None: return self.http_sender diff --git a/smartystreets_python_sdk/us_enrichment/__init__.py b/smartystreets_python_sdk/us_enrichment/__init__.py new file mode 100644 index 0000000..0d4b8bc --- /dev/null +++ b/smartystreets_python_sdk/us_enrichment/__init__.py @@ -0,0 +1,2 @@ +from .client import Client +from .response import * \ No newline at end of file diff --git a/smartystreets_python_sdk/us_enrichment/client.py b/smartystreets_python_sdk/us_enrichment/client.py new file mode 100644 index 0000000..6d4a153 --- /dev/null +++ b/smartystreets_python_sdk/us_enrichment/client.py @@ -0,0 +1,52 @@ +from smartystreets_python_sdk import Request +from smartystreets_python_sdk.exceptions import SmartyException +from .lookup import FinancialLookup, PrincipalLookup +from .response import Response + + +class Client: + def __init__(self, sender, serializer): + """ + It is recommended to instantiate this class using ClientBuilder.build_us_enrichment_api_client() + """ + self.sender = sender + self.serializer = serializer + + def send_property_financial_lookup(self, smartykey): + l = FinancialLookup(smartykey) + send_lookup(self, l) + return l.result + + def send_property_principal_lookup(self, smartykey): + l = PrincipalLookup(smartykey) + send_lookup(self, l) + return l.result + + +def send_lookup(client: Client, lookup): + """ + Sends a Lookup object to the US Enrichment API and stores the result in the Lookup's result field. + """ + if lookup is None or lookup.smartykey is None or not isinstance(lookup.smartykey, str) or len( + lookup.smartykey.strip()) == 0: + raise SmartyException('Client.send() requires a Lookup with the "smartykey" field set as a string') + + request = build_request(lookup) + + response = client.sender.send(request) + if response.error: + raise response.error + + response = client.serializer.deserialize(response.payload) + result = [] + for candidate in response: + result.append(Response(candidate)) + lookup.result = result + return result + + +def build_request(lookup): + request = Request() + request.url_prefix = lookup.smartykey + "/" + lookup.dataset + "/" + lookup.dataSubset + + return request diff --git a/smartystreets_python_sdk/us_enrichment/lookup.py b/smartystreets_python_sdk/us_enrichment/lookup.py new file mode 100644 index 0000000..f6b0206 --- /dev/null +++ b/smartystreets_python_sdk/us_enrichment/lookup.py @@ -0,0 +1,18 @@ +propertyDataset = "property" +financialDataSubset = "financial" +principalDataSubset = "principal" + +class Lookup: + def __init__(self, smartykey, dataset, dataSubset): + self.smartykey = smartykey + self.dataset = dataset + self.dataSubset = dataSubset + self.result = [] + +class FinancialLookup(Lookup): + def __init__(self, smartykey): + super().__init__(smartykey, propertyDataset, financialDataSubset) + +class PrincipalLookup(Lookup): + def __init__(self, smartykey): + super().__init__(smartykey, propertyDataset, principalDataSubset) \ No newline at end of file diff --git a/smartystreets_python_sdk/us_enrichment/response.py b/smartystreets_python_sdk/us_enrichment/response.py new file mode 100644 index 0000000..e4b2928 --- /dev/null +++ b/smartystreets_python_sdk/us_enrichment/response.py @@ -0,0 +1,530 @@ +class Response: + def __init__(self, obj): + self.smarty_key = obj.get('smarty_key', None) + self.data_set_name = obj.get('data_set_name', None) + self.data_subset_name = obj.get('data_subset_name', None) + self.attributes = get_attributes(self.data_set_name, self.data_subset_name, obj.get('attributes')) + + def __str__(self): + lines = [self.__class__.__name__ + ':'] + for key, val in vars(self).items(): + lines += '{}: {}'.format(key, val).split('\n') + return '\n '.join(lines) + + def __eq__(self, __value: object) -> bool: + return isinstance(__value, type(self)) and __value.smarty_key == self.smarty_key + + +def get_attributes(dataset, data_subset, attribute_obj): + if dataset == "property": + if data_subset == "financial": + return FinancialAttributes(attribute_obj) + if data_subset == "principal": + return PrincipalAttributes(attribute_obj) + + +class PrincipalAttributes: + def __init__(self, obj): + self.first_floor_sqft = obj.get('1st_floor_sqft', None) + self.second_floor_sqft = obj.get('2nd_floor_sqft', None) + self.acres = obj.get('acres', None) + self.address_info_privacy = obj.get('address_info_privacy', None) + self.air_conditioner = obj.get('air_conditioner', None) + self.arbor_pergola = obj.get('arbor_pergola', None) + self.assessed_improvement_percent = obj.get('assessed_improvement_percent', None) + self.assessed_improvement_value = obj.get('assessed_improvement_value', None) + self.assessed_land_value = obj.get('assessed_land_value', None) + self.assessed_value = obj.get('assessed_value', None) + self.assessor_last_update = obj.get('assessor_last_update', None) + self.assessor_taxroll_update = obj.get('assessor_taxroll_update', None) + self.attic_area = obj.get('attic_area', None) + self.attic_flag = obj.get('attic_flag', None) + self.balcony = obj.get('balcony', None) + self.balcony_area = obj.get('balcony_area', None) + self.basement_sqft = obj.get('basement_sqft', None) + self.basement_sqft_finished = obj.get('basement_sqft_finished', None) + self.basement_sqft_unfinished = obj.get('basement_sqft_unfinished', None) + self.bath_house = obj.get('bath_house', None) + self.bath_house_sqft = obj.get('bath_house_sqft', None) + self.bathrooms_partial = obj.get('bathrooms_partial', None) + self.bathrooms_total = obj.get('bathrooms_total', None) + self.bedrooms = obj.get('bedrooms', None) + self.block1 = obj.get('block1', None) + self.block2 = obj.get('block2', None) + self.boat_access = obj.get('boat_access', None) + self.boat_house = obj.get('boat_house', None) + self.boat_house_sqft = obj.get('boat_house_sqft', None) + self.boat_lift = obj.get('boat_lift', None) + self.bonus_room = obj.get('bonus_room', None) + self.breakfast_nook = obj.get('breakfast_nook', None) + self.breezeway = obj.get('breezeway', None) + self.building_definition_code = obj.get('building_definition_code', None) + self.building_sqft = obj.get('building_sqft', None) + self.cabin = obj.get('cabin', None) + self.cabin_sqft = obj.get('cabin_sqft', None) + self.canopy = obj.get('canopy', None) + self.canopy_sqft = obj.get('canopy_sqft', None) + self.carport = obj.get('carport', None) + self.carport_sqft = obj.get('carport_sqft', None) + self.cbsa_code = obj.get('cbsa_code', None) + self.cbsa_name = obj.get('cbsa_name', None) + self.cellar = obj.get('cellar', None) + self.census_block = obj.get('census_block', None) + self.census_block_group = obj.get('census_block_group', None) + self.census_fips_place_code = obj.get('census_fips_place_code', None) + self.census_tract = obj.get('census_tract', None) + self.central_vacuum = obj.get('central_vacuum', None) + self.code_title_company = obj.get('code_title_company', None) + self.combined_statistical_area = obj.get('combined_statistical_area', None) + self.community_rec = obj.get('community_rec', None) + self.company_flag = obj.get('company_flag', None) + self.congressional_district = obj.get('congressional_district', None) + self.contact_city = obj.get('contact_city', None) + self.contact_crrt = obj.get('contact_crrt', None) + self.contact_full_address = obj.get('contact_full_address', None) + self.contact_house_number = obj.get('contact_house_number', None) + self.contact_mail_info_format = obj.get('contact_mail_info_format', None) + self.contact_mail_info_privacy = obj.get('contact_mail_info_privacy', None) + self.contact_mailing_county = obj.get('contact_mailing_county', None) + self.contact_mailing_fips = obj.get('contact_mailing_fips', None) + self.contact_post_direction = obj.get('contact_post_direction', None) + self.contact_pre_direction = obj.get('contact_pre_direction', None) + self.contact_state = obj.get('contact_state', None) + self.contact_street_name = obj.get('contact_street_name', None) + self.contact_suffix = obj.get('contact_suffix', None) + self.contact_unit_designator = obj.get('contact_unit_designator', None) + self.contact_value = obj.get('contact_value', None) + self.contact_zip = obj.get('contact_zip', None) + self.contact_zip4 = obj.get('contact_zip4', None) + self.courtyard = obj.get('courtyard', None) + self.courtyard_area = obj.get('courtyard_area', None) + self.deck = obj.get('deck', None) + self.deck_area = obj.get('deck_area', None) + self.deed_document_page = obj.get('deed_document_page', None) + self.deed_document_book = obj.get('deed_document_book', None) + self.deed_document_number = obj.get('deed_document_number', None) + self.deed_owner_first_name = obj.get('deed_owner_first_name', None) + self.deed_owner_first_name2 = obj.get('deed_owner_first_name2', None) + self.deed_owner_first_name3 = obj.get('deed_owner_first_name3', None) + self.deed_owner_first_name4 = obj.get('deed_owner_first_name4', None) + self.deed_owner_full_name = obj.get('deed_owner_full_name', None) + self.deed_owner_full_name2 = obj.get('deed_owner_full_name2', None) + self.deed_owner_full_name3 = obj.get('deed_owner_full_name3', None) + self.deed_owner_full_name4 = obj.get('deed_owner_full_name4', None) + self.deed_owner_last_name = obj.get('deed_owner_last_name', None) + self.deed_owner_last_name2 = obj.get('deed_owner_last_name2', None) + self.deed_owner_last_name3 = obj.get('deed_owner_last_name3', None) + self.deed_owner_last_name4 = obj.get('deed_owner_last_name4', None) + self.deed_owner_middle_name = obj.get('deed_owner_middle_name', None) + self.deed_owner_middle_name = obj.get('deed_owner_middle_name', None) + self.deed_owner_middle_name2 = obj.get('deed_owner_middle_name2', None) + self.deed_owner_middle_name3 = obj.get('deed_owner_middle_name3', None) + self.deed_owner_middle_name4 = obj.get('deed_owner_middle_name4', None) + self.deed_owner_suffix = obj.get('deed_owner_suffix', None) + self.deed_owner_suffix2 = obj.get('deed_owner_suffix2', None) + self.deed_owner_suffix3 = obj.get('deed_owner_suffix3', None) + self.deed_owner_suffix4 = obj.get('deed_owner_suffix4', None) + self.deed_sale_date = obj.get('deed_sale_date', None) + self.deed_sale_price = obj.get('deed_sale_price', None) + self.deed_transaction_id = obj.get('deed_transaction_id', None) + self.depth_linear_footage = obj.get('depth_linear_footage', None) + self.disabled_tax_exemption = obj.get('disabled_tax_exemption', None) + self.driveway_sqft = obj.get('driveway_sqft', None) + self.driveway_type = obj.get('driveway_type', None) + self.effective_year_built = obj.get('effective_year_built', None) + self.elevation_feet = obj.get('elevation_feet', None) + self.elevator = obj.get('elevator', None) + self.equestrian_arena = obj.get('equestrian_arena', None) + self.escalator = obj.get('escalator', None) + self.exercise_room = obj.get('exercise_room', None) + self.exterior_walls = obj.get('exterior_walls', None) + self.family_room = obj.get('family_room', None) + self.fence = obj.get('fence', None) + self.fence_area = obj.get('fence_area', None) + self.fips_code = obj.get('fips_code', None) + self.fire_resistance_code = obj.get('fire_resistance_code', None) + self.fire_sprinklers_flag = obj.get('fire_sprinklers_flag', None) + self.fireplace = obj.get('fireplace', None) + self.fireplace_number = obj.get('fireplace_number', None) + self.first_name = obj.get('first_name', None) + self.first_name_2 = obj.get('first_name_2', None) + self.first_name_3 = obj.get('first_name_3', None) + self.first_name_4 = obj.get('first_name_4', None) + self.flooring = obj.get('flooring', None) + self.foundation = obj.get('foundation', None) + self.game_room = obj.get('game_room', None) + self.garage = obj.get('garage', None) + self.garage_sqft = obj.get('garage_sqft', None) + self.gazebo = obj.get('gazebo', None) + self.gazebo_sqft = obj.get('gazebo_sqft', None) + self.golf_course = obj.get('golf_course', None) + self.grainery = obj.get('grainery', None) + self.grainery_sqft = obj.get('grainery_sqft', None) + self.great_room = obj.get('great_room', None) + self.greenhouse = obj.get('greenhouse', None) + self.greenhouse_sqft = obj.get('greenhouse_sqft', None) + self.gross_sqft = obj.get('gross_sqft', None) + self.guesthouse = obj.get('guesthouse', None) + self.guesthouse_sqft = obj.get('guesthouse_sqft', None) + self.handicap_accessibility = obj.get('handicap_accessibility', None) + self.heat = obj.get('heat', None) + self.heat_fuel_type = obj.get('heat_fuel_type', None) + self.hobby_room = obj.get('hobby_room', None) + self.homeowner_tax_exemption = obj.get('homeowner_tax_exemption', None) + self.instrument_date = obj.get('instrument_date', None) + self.intercom_system = obj.get('intercom_system', None) + self.interest_rate_type_2 = obj.get('interest_rate_type_2', None) + self.interior_structure = obj.get('interior_structure', None) + self.kennel = obj.get('kennel', None) + self.kennel_sqft = obj.get('kennel_sqft', None) + self.land_use_code = obj.get('land_use_code', None) + self.land_use_group = obj.get('land_use_group', None) + self.land_use_standard = obj.get('land_use_standard', None) + self.last_name = obj.get('last_name', None) + self.last_name_2 = obj.get('last_name_2', None) + self.last_name_3 = obj.get('last_name_3', None) + self.last_name_4 = obj.get('last_name_4', None) + self.latitude = obj.get('latitude', None) + self.laundry = obj.get('laundry', None) + self.lean_to = obj.get('lean_to', None) + self.lean_to_sqft = obj.get('lean_to_sqft', None) + self.legal_description = obj.get('legal_description', None) + self.legal_unit = obj.get('legal_unit', None) + self.lender_address = obj.get('lender_address', None) + self.lender_address_2 = obj.get('lender_address_2', None) + self.lender_city = obj.get('lender_city', None) + self.lender_city_2 = obj.get('lender_city_2', None) + self.lender_code_2 = obj.get('lender_code_2', None) + self.lender_first_name = obj.get('lender_first_name', None) + self.lender_first_name_2 = obj.get('lender_first_name_2', None) + self.lender_last_name = obj.get('lender_last_name', None) + self.lender_last_name_2 = obj.get('lender_last_name_2', None) + self.lender_name = obj.get('lender_name', None) + self.lender_name_2 = obj.get('lender_name_2', None) + self.lender_seller_carry_back = obj.get('lender_seller_carry_back', None) + self.lender_seller_carry_back_2 = obj.get('lender_seller_carry_back_2', None) + self.lender_state = obj.get('lender_state', None) + self.lender_state_2 = obj.get('lender_state_2', None) + self.lender_zip = obj.get('lender_zip', None) + self.lender_zip_2 = obj.get('lender_zip_2', None) + self.lender_zip_extended = obj.get('lender_zip_extended', None) + self.lender_zip_extended_2 = obj.get('lender_zip_extended_2', None) + self.loading_platform = obj.get('loading_platform', None) + self.loading_platform_sqft = obj.get('loading_platform_sqft', None) + self.longitude = obj.get('longitude', None) + self.lot_1 = obj.get('lot_1', None) + self.lot_2 = obj.get('lot_2', None) + self.lot_3 = obj.get('lot_3', None) + self.lot_sqft = obj.get('lot_sqft', None) + self.market_improvement_percent = obj.get('market_improvement_percent', None) + self.market_improvement_value = obj.get('market_improvement_value', None) + self.market_land_value = obj.get('market_land_value', None) + self.market_value_year = obj.get('market_value_year', None) + self.match_type = obj.get('match_type', None) + self.media_room = obj.get('media_room', None) + self.metro_division = obj.get('metro_division', None) + self.middle_name = obj.get('middle_name', None) + self.middle_name_2 = obj.get('middle_name_2', None) + self.middle_name_3 = obj.get('middle_name_3', None) + self.middle_name_4 = obj.get('middle_name_4', None) + self.milkhouse = obj.get('milkhouse', None) + self.milkhouse_sqft = obj.get('milkhouse_sqft', None) + self.minor_civil_division_code = obj.get('minor_civil_division_code', None) + self.minor_civil_division_name = obj.get('minor_civil_division_name', None) + self.mobile_home_hookup = obj.get('mobile_home_hookup', None) + self.mortgage_amount = obj.get('mortgage_amount', None) + self.mortgage_amount_2 = obj.get('mortgage_amount_2', None) + self.mortgage_due_date = obj.get('mortgage_due_date', None) + self.mortgage_due_date_2 = obj.get('mortgage_due_date_2', None) + self.mortgage_interest_rate = obj.get('mortgage_interest_rate', None) + self.mortgage_interest_rate_type = obj.get('mortgage_interest_rate_type', None) + self.mortgage_lender_code = obj.get('mortgage_lender_code', None) + self.mortgage_rate_2 = obj.get('mortgage_rate_2', None) + self.mortgage_recording_date = obj.get('mortgage_recording_date', None) + self.mortgage_recording_date_2 = obj.get('mortgage_recording_date_2', None) + self.mortgage_term = obj.get('mortgage_term', None) + self.mortgage_term_2 = obj.get('mortgage_term_2', None) + self.mortgage_term_type = obj.get('mortgage_term_type', None) + self.mortgage_term_type_2 = obj.get('mortgage_term_type_2', None) + self.mortgage_type = obj.get('mortgage_type', None) + self.mortgage_type_2 = obj.get('mortgage_type_2', None) + self.msa_code = obj.get('msa_code', None) + self.msa_name = obj.get('msa_name', None) + self.mud_room = obj.get('mud_room', None) + self.multi_parcel_flag = obj.get('multi_parcel_flag', None) + self.name_title_company = obj.get('name_title_company', None) + self.neighborhood_code = obj.get('neighborhood_code', None) + self.number_of_buildings = obj.get('number_of_buildings', None) + self.office = obj.get('office', None) + self.office_sqft = obj.get('office_sqft', None) + self.other_tax_exemption = obj.get('other_tax_exemption', None) + self.outdoor_kitchen_fireplace = obj.get('outdoor_kitchen_fireplace', None) + self.overhead_door = obj.get('overhead_door', None) + self.owner_full_name = obj.get('owner_full_name', None) + self.owner_full_name_2 = obj.get('owner_full_name_2', None) + self.owner_full_name_3 = obj.get('owner_full_name_3', None) + self.owner_full_name_4 = obj.get('owner_full_name_4', None) + self.owner_occupancy_status = obj.get('owner_occupancy_status', None) + self.ownership_transfer_date = obj.get('ownership_transfer_date', None) + self.ownership_transfer_doc_number = obj.get('ownership_transfer_doc_number', None) + self.ownership_transfer_transaction_id = obj.get('ownership_transfer_transaction_id', None) + self.ownership_type = obj.get('ownership_type', None) + self.ownership_type_2 = obj.get('ownership_type_2', None) + self.ownership_vesting_relation_code = obj.get('ownership_vesting_relation_code', None) + self.parcel_account_number = obj.get('parcel_account_number', None) + self.parcel_map_book = obj.get('parcel_map_book', None) + self.parcel_map_page = obj.get('parcel_map_page', None) + self.parcel_number_alternate = obj.get('parcel_number_alternate', None) + self.parcel_number_formatted = obj.get('parcel_number_formatted', None) + self.parcel_number_previous = obj.get('parcel_number_previous', None) + self.parcel_number_year_added = obj.get('parcel_number_year_added', None) + self.parcel_number_year_change = obj.get('parcel_number_year_change', None) + self.parcel_raw_number = obj.get('parcel_raw_number', None) + self.parcel_shell_record = obj.get('parcel_shell_record', None) + self.parking_spaces = obj.get('parking_spaces', None) + self.patio_area = obj.get('patio_area', None) + self.phase_name = obj.get('phase_name', None) + self.plumbing_fixtures_count = obj.get('plumbing_fixtures_count', None) + self.pole_struct = obj.get('pole_struct', None) + self.pole_struct_sqft = obj.get('pole_struct_sqft', None) + self.pond = obj.get('pond', None) + self.pool = obj.get('pool', None) + self.pool_area = obj.get('pool_area', None) + self.poolhouse = obj.get('poolhouse', None) + self.poolhouse_sqft = obj.get('poolhouse_sqft', None) + self.porch = obj.get('porch', None) + self.porch_area = obj.get('porch_area', None) + self.poultry_house = obj.get('poultry_house', None) + self.poultry_house_sqft = obj.get('poultry_house_sqft', None) + self.previous_assessed_value = obj.get('previous_assessed_value', None) + self.prior_sale_amount = obj.get('prior_sale_amount', None) + self.prior_sale_date = obj.get('prior_sale_date', None) + self.property_address_carrier_route_code = obj.get('property_address_carrier_route_code', None) + self.property_address_city = obj.get('property_address_city', None) + self.property_address_full = obj.get('property_address_full', None) + self.property_address_house_number = obj.get('property_address_house_number', None) + self.property_address_post_direction = obj.get('property_address_post_direction', None) + self.property_address_pre_direction = obj.get('property_address_pre_direction', None) + self.property_address_state = obj.get('property_address_state', None) + self.property_address_street_name = obj.get('property_address_street_name', None) + self.property_address_street_suffix = obj.get('property_address_street_suffix', None) + self.property_address_unit_designator = obj.get('property_address_unit_designator', None) + self.property_address_unit_value = obj.get('property_address_unit_value', None) + self.property_address_zip_4 = obj.get('property_address_zip_4', None) + self.property_address_zipcode = obj.get('property_address_zipcode', None) + self.publication_date = obj.get('publication_date', None) + self.quarter = obj.get('quarter', None) + self.quarter_quarter = obj.get('quarter_quarter', None) + self.quonset = obj.get('quonset', None) + self.quonset_sqft = obj.get('quonset_sqft', None) + self.range = obj.get('range', None) + self.recording_date = obj.get('recording_date', None) + self.roof_cover = obj.get('roof_cover', None) + self.roof_frame = obj.get('roof_frame', None) + self.rooms = obj.get('rooms', None) + self.rv_parking = obj.get('rv_parking', None) + self.safe_room = obj.get('safe_room', None) + self.sale_amount = obj.get('sale_amount', None) + self.sale_date = obj.get('sale_date', None) + self.sauna = obj.get('sauna', None) + self.section = obj.get('section', None) + self.security_alarm = obj.get('security_alarm', None) + self.senior_tax_exemption = obj.get('senior_tax_exemption', None) + self.sewer_type = obj.get('sewer_type', None) + self.shed = obj.get('shed', None) + self.shed_sqft = obj.get('shed_sqft', None) + self.silo = obj.get('silo', None) + self.silo_sqft = obj.get('silo_sqft', None) + self.sitting_room = obj.get('sitting_room', None) + self.situs_county = obj.get('situs_county', None) + self.situs_state = obj.get('situs_state', None) + self.sound_system = obj.get('sound_system', None) + self.sports_court = obj.get('sports_court', None) + self.sprinklers = obj.get('sprinklers', None) + self.stable = obj.get('stable', None) + self.stable_sqft = obj.get('stable_sqft', None) + self.storage_building = obj.get('storage_building', None) + self.storage_building_sqft = obj.get('storage_building_sqft', None) + self.stories_number = obj.get('stories_number', None) + self.storm_shelter = obj.get('storm_shelter', None) + self.storm_shutter = obj.get('storm_shutter', None) + self.structure_style = obj.get('structure_style', None) + self.study = obj.get('study', None) + self.subdivision = obj.get('subdivision', None) + self.suffix = obj.get('suffix', None) + self.suffix_2 = obj.get('suffix_2', None) + self.suffix_3 = obj.get('suffix_3', None) + self.suffix_4 = obj.get('suffix_4', None) + self.sunroom = obj.get('sunroom', None) + self.tax_assess_year = obj.get('tax_assess_year', None) + self.tax_billed_amount = obj.get('tax_billed_amount', None) + self.tax_delinquent_year = obj.get('tax_delinquent_year', None) + self.tax_fiscal_year = obj.get('tax_fiscal_year', None) + self.tax_jurisdiction = obj.get('tax_jurisdiction', None) + self.tax_rate_area = obj.get('tax_rate_area', None) + self.tennis_court = obj.get('tennis_court', None) + self.topography_code = obj.get('topography_code', None) + self.total_market_value = obj.get('total_market_value', None) + self.township = obj.get('township', None) + self.tract_number = obj.get('tract_number', None) + self.transfer_amount = obj.get('transfer_amount', None) + self.trust_description = obj.get('trust_description', None) + self.unit_count = obj.get('unit_count', None) + self.upper_floors_sqft = obj.get('upper_floors_sqft', None) + self.utility = obj.get('utility', None) + self.utility_building = obj.get('utility_building', None) + self.utility_building_sqft = obj.get('utility_building_sqft', None) + self.utility_sqft = obj.get('utility_sqft', None) + self.veteran_tax_exemption = obj.get('veteran_tax_exemption', None) + self.view_description = obj.get('view_description', None) + self.water_feature = obj.get('water_feature', None) + self.water_service_type = obj.get('water_service_type', None) + self.wet_bar = obj.get('wet_bar', None) + self.widow_tax_exemption = obj.get('widow_tax_exemption', None) + self.width_linear_footage = obj.get('width_linear_footage', None) + self.wine_cellar = obj.get('wine_cellar', None) + self.year_built = obj.get('year_built', None) + self.zoning = obj.get('zoning', None) + + def __str__(self): + return self.__dict__.__str__() + + +class FinancialAttributes: + def __init__(self, obj): + self.assessed_improvement_percent = obj.get('assessed_improvement_percent', None) + self.assessed_improvement_value = obj.get('assessed_improvement_value', None) + self.assessed_land_value = obj.get('assessed_land_value', None) + self.assessed_value = obj.get('assessed_value', None) + self.assessor_last_update = obj.get('assessor_last_update', None) + self.assessor_taxroll_update = obj.get('assessor_taxroll_update', None) + self.contact_city = obj.get('contact_city', None) + self.contact_crrt = obj.get('contact_crrt', None) + self.contact_full_address = obj.get('contact_full_address', None) + self.contact_house_number = obj.get('contact_house_number', None) + self.contact_mail_info_format = obj.get('contact_mail_info_format', None) + self.contact_mail_info_privacy = obj.get('contact_mail_info_privacy', None) + self.contact_mailing_county = obj.get('contact_mailing_county', None) + self.contact_mailing_fips = obj.get('contact_mailing_fips', None) + self.contact_post_direction = obj.get('contact_post_direction', None) + self.contact_pre_direction = obj.get('contact_pre_direction', None) + self.contact_state = obj.get('contact_state', None) + self.contact_street_name = obj.get('contact_street_name', None) + self.contact_suffix = obj.get('contact_suffix', None) + self.contact_unit_designator = obj.get('contact_unit_designator', None) + self.contact_value = obj.get('contact_value', None) + self.contact_zip = obj.get('contact_zip', None) + self.contact_zip4 = obj.get('contact_zip4', None) + self.deed_document_page = obj.get('deed_document_page', None) + self.deed_document_book = obj.get('deed_document_book', None) + self.deed_document_number = obj.get('deed_document_number', None) + self.deed_owner_first_name = obj.get('deed_owner_first_name', None) + self.deed_owner_first_name2 = obj.get('deed_owner_first_name2', None) + self.deed_owner_first_name3 = obj.get('deed_owner_first_name3', None) + self.deed_owner_first_name4 = obj.get('deed_owner_first_name4', None) + self.deed_owner_full_name = obj.get('deed_owner_full_name', None) + self.deed_owner_full_name2 = obj.get('deed_owner_full_name2', None) + self.deed_owner_full_name3 = obj.get('deed_owner_full_name3', None) + self.deed_owner_full_name4 = obj.get('deed_owner_full_name4', None) + self.deed_owner_last_name = obj.get('deed_owner_last_name', None) + self.deed_owner_last_name2 = obj.get('deed_owner_last_name2', None) + self.deed_owner_last_name3 = obj.get('deed_owner_last_name3', None) + self.deed_owner_last_name4 = obj.get('deed_owner_last_name4', None) + self.deed_owner_middle_name = obj.get('deed_owner_middle_name', None) + self.deed_owner_middle_name2 = obj.get('deed_owner_middle_name2', None) + self.deed_owner_middle_name3 = obj.get('deed_owner_middle_name3', None) + self.deed_owner_middle_name4 = obj.get('deed_owner_middle_name4', None) + self.deed_owner_suffix = obj.get('deed_owner_suffix', None) + self.deed_owner_suffix2 = obj.get('deed_owner_suffix2', None) + self.deed_owner_suffix3 = obj.get('deed_owner_suffix3', None) + self.deed_owner_suffix4 = obj.get('deed_owner_suffix4', None) + self.deed_sale_date = obj.get('deed_sale_date', None) + self.deed_sale_price = obj.get('deed_sale_price', None) + self.deed_transaction_id = obj.get('deed_transaction_id', None) + self.disabled_tax_exemption = obj.get('disabled_tax_exemption', None) + self.financial_history = get_financial_history(obj.get('financial_history', None)) + self.homeowner_tax_exemption = obj.get('homeowner_tax_exemption', None) + self.market_improvement_percent = obj.get('market_improvement_percent', None) + self.market_improvement_value = obj.get('market_improvement_value', None) + self.market_land_value = obj.get('market_land_value', None) + self.market_value_year = obj.get('market_value_year', None) + self.match_type = obj.get('match_type', None) + self.other_tax_exemption = obj.get('other_tax_exemption', None) + self.ownership_transfer_date = obj.get('ownership_transfer_date', None) + self.ownership_transfer_doc_number = obj.get('ownership_transfer_doc_number', None) + self.ownership_transfer_transaction_id = obj.get('ownership_transfer_transaction_id', None) + self.ownership_type = obj.get('ownership_type', None) + self.ownership_type_2 = obj.get('ownership_type_2', None) + self.previous_assessed_value = obj.get('previous_assessed_value', None) + self.prior_sale_amount = obj.get('prior_sale_amount', None) + self.prior_sale_date = obj.get('prior_sale_date', None) + self.sale_amount = obj.get('sale_amount', None) + self.sale_date = obj.get('sale_date', None) + self.senior_tax_exemption = obj.get('senior_tax_exemption', None) + self.tax_assess_year = obj.get('tax_assess_year', None) + self.tax_billed_amount = obj.get('tax_billed_amount', None) + self.tax_delinquent_year = obj.get('tax_delinquent_year', None) + self.tax_fiscal_year = obj.get('tax_fiscal_year', None) + self.tax_rate_area = obj.get('tax_rate_area', None) + self.total_market_value = obj.get('total_market_value', None) + self.trust_description = obj.get('trust_description', None) + self.veteran_tax_exemption = obj.get('veteran_tax_exemption', None) + self.widow_tax_exemption = obj.get('widow_tax_exemption', None) + + def __str__(self): + return self.__dict__.__str__() + + +def get_financial_history(financial_history_obj): + if financial_history_obj is None: + return None + output = [] + for obj in financial_history_obj: + output.append(FinancialHistory(obj)) + return output + + +class FinancialHistory: + def __init__(self, obj): + self.code_title_company = obj.get('code_title_company', None) + self.instrument_date = obj.get('instrument_date', None) + self.interest_rate_type_2 = obj.get('interest_rate_type_2', None) + self.lender_address = obj.get('lender_address', None) + self.lender_address_2 = obj.get('lender_address_2', None) + self.lender_city = obj.get('lender_city', None) + self.lender_city_2 = obj.get('lender_city_2', None) + self.lender_code_2 = obj.get('lender_code_2', None) + self.lender_first_name = obj.get('lender_first_name', None) + self.lender_first_name_2 = obj.get('lender_first_name_2', None) + self.lender_last_name = obj.get('lender_last_name', None) + self.lender_last_name_2 = obj.get('lender_last_name_2', None) + self.lender_name = obj.get('lender_name', None) + self.lender_name_2 = obj.get('lender_name_2', None) + self.lender_seller_carry_back = obj.get('lender_seller_carry_back', None) + self.lender_seller_carry_back_2 = obj.get('lender_seller_carry_back_2', None) + self.lender_state = obj.get('lender_state', None) + self.lender_state_2 = obj.get('lender_state_2', None) + self.lender_zip = obj.get('lender_zip', None) + self.lender_zip_2 = obj.get('lender_zip_2', None) + self.lender_zip_extended = obj.get('lender_zip_extended', None) + self.lender_zip_extended_2 = obj.get('lender_zip_extended_2', None) + self.mortgage_amount = obj.get('mortgage_amount', None) + self.mortgage_amount_2 = obj.get('mortgage_amount_2', None) + self.mortgage_due_date = obj.get('mortgage_due_date', None) + self.mortgage_due_date_2 = obj.get('mortgage_due_date_2', None) + self.mortgage_interest_rate = obj.get('mortgage_interest_rate', None) + self.mortgage_interest_rate_type = obj.get('mortgage_interest_rate_type', None) + self.mortgage_lender_code = obj.get('mortgage_lender_code', None) + self.mortgage_rate_2 = obj.get('mortgage_rate_2', None) + self.mortgage_recording_date = obj.get('mortgage_recording_date', None) + self.mortgage_recording_date_2 = obj.get('mortgage_recording_date_2', None) + self.mortgage_term = obj.get('mortgage_term', None) + self.mortgage_term_2 = obj.get('mortgage_term_2', None) + self.mortgage_term_type = obj.get('mortgage_term_type', None) + self.mortgage_term_type_2 = obj.get('mortgage_term_type_2', None) + self.mortgage_type = obj.get('mortgage_type', None) + self.mortgage_type_2 = obj.get('mortgage_type_2', None) + self.multi_parcel_flag = obj.get('multi_parcel_flag', None) + self.name_title_company = obj.get('name_title_company', None) + self.recording_date = obj.get('recording_date', None) + self.transfer_amount = obj.get('transfer_amount', None) diff --git a/test/us_enrichment/__init__.py b/test/us_enrichment/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/us_enrichment/client_test.py b/test/us_enrichment/client_test.py new file mode 100644 index 0000000..a6eefc2 --- /dev/null +++ b/test/us_enrichment/client_test.py @@ -0,0 +1,40 @@ +import unittest + +from smartystreets_python_sdk import URLPrefixSender +from smartystreets_python_sdk.us_enrichment.client import Client, send_lookup +from smartystreets_python_sdk.us_enrichment.lookup import FinancialLookup, PrincipalLookup +from smartystreets_python_sdk.us_enrichment.response import Response +from test.mocks import * + + +class TestClient(unittest.TestCase): + def test_sending_Financial_Lookup(self): + capturing_sender = RequestCapturingSender() + sender = URLPrefixSender('http://localhost/', capturing_sender) + serializer = FakeSerializer(None) + client = Client(sender, serializer) + lookup = FinancialLookup("xxx") + result = send_lookup(client, lookup) + + self.assertEqual("property", lookup.dataset) + self.assertEqual("financial", lookup.dataSubset) + self.assertEqual(lookup.result, result) + + function_result = client.send_property_financial_lookup("xxx") + self.assertEqual(result, function_result) + + def test_sending_principal_lookup(self): + capturing_sender = RequestCapturingSender() + sender = URLPrefixSender('http://localhost/', capturing_sender) + serializer = FakeSerializer(None) + client = Client(sender, serializer) + + lookup = PrincipalLookup("xxx") + result = send_lookup(client, lookup) + + self.assertEqual("property", lookup.dataset) + self.assertEqual("principal", lookup.dataSubset) + self.assertEqual(lookup.result, result) + + function_result = client.send_property_principal_lookup("xxx") + self.assertEqual(result, function_result) diff --git a/test/us_enrichment/result_test.py b/test/us_enrichment/result_test.py new file mode 100644 index 0000000..30cdf58 --- /dev/null +++ b/test/us_enrichment/result_test.py @@ -0,0 +1,50 @@ +import unittest + +from smartystreets_python_sdk.us_enrichment.response import Response + +name_changes = {"1st_floor_sqft": "first_floor_sqft", "2nd_floor_sqft": "second_floor_sqft"} + +class ResultTest(unittest.TestCase): + def test_result_generic_deserialization(self): + test_obj = {"smarty_key":"179317873","data_set_name":"property","data_subset_name":"principal", + "attributes":{"1st_floor_sqft":"1169","2nd_floor_sqft":"400","acres":"18.0000000", + "assessed_improvement_percent":"0.00","assessed_improvement_value":"0", + "assessed_land_value":"0","assessed_value":"125236","assessor_last_update":"2022-12-14", + "assessor_taxroll_update":"2022-09-30","attic_area":"400","basement_sqft":"0", + "bathrooms_partial":"1","bathrooms_total":"2.000","bedrooms":"1","building_definition_code":"living_area", + "building_sqft":"2034","cbsa_code":"38900","cbsa_name":"Portland-Vancouver-Beaverton, OR-WA Metropolitan Statistical Area", + "census_block":"1038","census_block_group":"1","census_tract":"24100","code_title_company":"0","combined_statistical_area":"Portland-Vancouver-Salem, OR-WA", + "congressional_district":"331","contact_city":"Colton","contact_crrt":"R002","contact_full_address":"19768 S Harper Rd", + "contact_house_number":"19768","contact_mail_info_format":"standard_us","contact_mailing_county":"Clackamas","contact_mailing_fips":"41005", + "contact_pre_direction":"S","contact_state":"OR","contact_street_name":"Harper","contact_suffix":"Rd","contact_zip":"97017", + "contact_zip4":"9417","deck_area":"0","deed_owner_first_name":"Lonny","deed_owner_full_name":"Lonny S Johnson","deed_owner_last_name":"Johnson", + "deed_owner_middle_name":"S","deed_sale_price":"0","depth_linear_footage":"0.0","driveway_sqft":"0","elevation_feet":"708","exterior_walls":"other", + "fence_area":"0","fips_code":"41005","fireplace":"yes","fireplace_number":"1","first_name":"Lonny","foundation":"block_unspecified", + "garage_sqft":"0","gross_sqft":"2034","heat":"forced_air","instrument_date":"2018-06-01","land_use_code":"12","land_use_group":"residential", + "land_use_standard":"rural_residence","last_name":"Johnson","latitude":"45.162895","legal_description":"Section 05 Township 5s Range 3e Tax Lot 02900", + "loading_platform":"0","longitude":"-122.459463","lot_1":"2900","lot_sqft":"784080.00","market_improvement_percent":"19.00","market_improvement_value":"152680", + "market_land_value":"618692","market_value_year":"2022","match_type":"parcel","middle_name":"S","minor_civil_division_code":"90595","minor_civil_division_name":"Colton", + "msa_code":"38900","msa_name":"Portland-Vancouver-Beaverton, OR-WA","multi_parcel_flag":"0","neighborhood_code":"14","owner_full_name":"Lonny S Johnson", + "owner_occupancy_status":"owner_occupied","ownership_type":"Individual","parcel_account_number":"01116946","parcel_number_year_added":"1993", + "parcel_number_year_change":"0","parcel_raw_number":"53e05 02900","parking_spaces":"0","patio_area":"0","plumbing_fixtures_count":"0","pool_area":"0", + "porch_area":"0","previous_assessed_value":"121694","property_address_carrier_route_code":"R002","property_address_city":"Colton","property_address_full":"19768 S Harper Rd", + "property_address_house_number":"19768","property_address_pre_direction":"S","property_address_state":"OR","property_address_street_name":"Harper", + "property_address_street_suffix":"Rd","property_address_zip_4":"9417","property_address_zipcode":"97017","publication_date":"2023-08-05", + "range":"3e","recording_date":"2018-06-04","roof_cover":"composition_shingle","rooms":"0","sale_amount":"0","section":"05","situs_county":"Clackamas", + "situs_state":"OR","stories_number":"1","tax_assess_year":"2022","tax_billed_amount":"1580.29","tax_fiscal_year":"2022","tax_jurisdiction":"Clackamas", + "tax_rate_area":"053-007","total_market_value":"771372","township":"5s","tract_number":"0","transfer_amount":"0","unit_count":"0","upper_floors_sqft":"0", + "width_linear_footage":"0.0","year_built":"1935","zoning":"Agf" + } + } + + res = Response(test_obj) + for key in test_obj.keys(): + if key == "attributes": + attributes_obj = test_obj[key] + for atr_key in attributes_obj.keys(): + other_key = atr_key + if atr_key in name_changes.keys(): + other_key = name_changes[atr_key] + self.assertEqual(attributes_obj[atr_key], res.attributes.__dict__[other_key]) + else: + self.assertEqual(test_obj[key], eval(f"res.{key}"))