From 8a31513c27cf758c3830a8ecd6febb4b04e5fe70 Mon Sep 17 00:00:00 2001 From: Christopher Date: Wed, 11 Apr 2018 14:26:28 +0200 Subject: [PATCH 1/4] updated module to reflect updated API - API_KEY instead of APP_ID and APP_SECRET - clean Resources sub-classes - fix Gadgets - extra comments - Person object --- env.sh | 3 +- main.py | 12 ++ root/exceptions.py | 12 +- root/insurance.py | 402 +++++++++++++++++++++++++++--------------- root/main.py | 10 -- tests/test_gadgets.py | 5 +- 6 files changed, 283 insertions(+), 161 deletions(-) create mode 100644 main.py delete mode 100644 root/main.py diff --git a/env.sh b/env.sh index c7619f5..1ffbd8c 100644 --- a/env.sh +++ b/env.sh @@ -1,3 +1,2 @@ # ENV variables for app -export ROOT_APP_SECRET="__replace__"; -export ROOT_APP_ID="__replace__"; +export ROOT_API_KEY="__replace__"; diff --git a/main.py b/main.py new file mode 100644 index 0000000..46c917b --- /dev/null +++ b/main.py @@ -0,0 +1,12 @@ +from root.insurance import Client + + +def main(): + # start insurance client + client = Client() + phone = "iPhone 6 Plus 128GB LTE" + print(f"The value of a {phone} is R{client.gadgets.get_phone_value(phone)}") + + +if __name__ == "__main__": + main() diff --git a/root/exceptions.py b/root/exceptions.py index 09d6c4a..2904498 100644 --- a/root/exceptions.py +++ b/root/exceptions.py @@ -2,11 +2,21 @@ class RootException(Exception): """Base class for exceptions in this module.""" pass + class RootCredentialsException(RootException): """Raised when library/wrapper is used without credentials set in the env variables. Attributes: message -- explanation of why the specific transition is not allowed """ - def __init__(self, message="No APP_ID and APP_SECRET set in environment variables"): + + def __init__(self, message="No ROOT_API_KEY set in environment variables"): + pass + +class RootIdentificationException(RootException): + """Raised when given identification is not syntactically correct + Attributes: + message -- explanation of why syntax was wrong + """ + def __init__(self, message="identification provided was ill-formed"): pass diff --git a/root/insurance.py b/root/insurance.py index 61507c4..314a18b 100644 --- a/root/insurance.py +++ b/root/insurance.py @@ -1,66 +1,236 @@ import requests import logging import os -from .exceptions import RootCredentialsException +from exceptions import RootCredentialsException, RootIdentificationException logging.basicConfig(level=logging.DEBUG) + class Client: - def __init__(self): - self.sandBox = os.environ.get('ROOT_SANDBOX', True) - self.production = False if self.sandBox else True - self.prodUrl = "https://api.root.co.za/v1/insurance" - self.baseURL = "https://sandbox.root.co.za/v1/insurance" if self.sandBox else self.prodUrl - self.appSecret = os.environ.get('ROOT_APP_SECRET') if not self.sandBox else "" - self.appID = os.environ.get('ROOT_APP_ID') if not self.sandBox else os.environ.get('ROOT_APP_SECRET') + def __init__(self, api_key=False): + """Provide an interface to access the Root Insurance API. + + With an interface, client=Client(), one can get + 1. Quotes (client.quotes), such as for Gadgets (client.gadgets) + 2. Policy Holders (client.policyholders) + 3. Applications (client.applications) + 4. Policies (client.policies) + + + :param api_key: + """ + self.sandbox = os.environ.get('ROOT_SANDBOX', True) + self.production = False if self.sandbox else True + self.prod_url = "https://api.root.co.za/v1/insurance" + self.base_url = "https://sandbox.root.co.za/v1/insurance" if self.sandbox else self.prod_url + self.api_key = os.environ.get('ROOT_API_KEY', api_key) self.applications = Applications(self) self.claims = Claims(self) self.policyholders = PolicyHolders(self) self.policies = Policies(self) self.gadgets = Gadgets(self) self.quotes = Quotes(self) - if not self.appID and not self.appSecret: + if not self.api_key: raise RootCredentialsException - print("[WARNING] Running in production mode: {mode}".format(mode=self.production)) + print("[{warning}] Running in production mode: {mode}".format(warning="WARNING" if self.production else "INFO", + mode=self.production)) def call(self, method, path, params=None, **kwargs): - resp = requests.request(method, - f'{self.baseURL}/{path}', - params=params, - headers={"Content-Type": "application/json"}, - auth=(self.appID, self.appSecret), + """Send a request to the Root API. + :param method: any method supported by requests lib. + :param path: url of api from root.co.za/{version}/insurance/{path} + :param params: optional params + :param kwargs: other keywords of note include 'json' for payload + :return: JSON-like response + """ + resp = requests.request(method, + '{base_url}/{path}'.format(base_url=self.base_url, path=path), + params=params, + headers={"Content-Type": "application/json"}, + auth=(self.api_key, ""), **kwargs ) - if resp.status_code == 200 or resp.status_code == 201: + if resp.status_code == requests.codes.ok: return resp.json() - raise Exception(resp.status_code, resp.json()) + logging.error('{} {}'.format(resp.status_code, resp.text)) + if self.sandbox: + resp.raise_for_status() -class Resource: - def __init__(self, client): +class Resource(object): + """ + Super class for + """ + + def __init__(self, client, method, path): self.client = client - - def call(self, method, path, params=None, **kwargs): - return self.client.call(method, path, params, **kwargs) + self.method = method + self.path = path + + def call(self, method=None, path="", params=None, **kwargs): + if method is None: + method = self.method + full_path = "{}/{}".format(self.path, path) if path != "" else self.path + return self.client.call(method, full_path, params, **kwargs) + + +class Person(object): + """An object for a person's details in the format the Root Insurance API wants + + # create object + >>> person = Person(Person.id_data('id','6801015800084','ZA'),...) + # send in correct format using + >>> data = person.details() + >>> data['extra'] = 'extra details' + >>> Resource.call(json=data) + + """ + + def __init__(self, id_object, first_name, last_name, date_of_birth=None, gender=None, email=None, cellphone=None, + *args, **kwargs): + if not ('type' in id_object and 'number' in id_object and 'country' in id_object): + raise RootIdentificationException() + + self._details = {'id': id_object, + 'first_name': first_name, + 'last_name': last_name} + if date_of_birth is not None: + self._details['date_of_birth'] = date_of_birth + if gender is not None: + self._details['gender'] = gender + if email is not None: + self._details['email'] = email + if cellphone is not None: + self._details['cellphone'] = cellphone + for kw, val in kwargs.items(): + self._details[kw] = val + + @staticmethod + def id_data(id_type: str, id_num: str, country_code: str): + """Helper method to correctly form the 'id' object dict + + Example: + person = Person(Person.id_data('id','6801015800084','ZA'),...) + + API docs: + type string. Either 'id' or 'passport'. + number string. The ID or passport number. + country string. The ISO Alpha-2 country code of the country of the id/passport number. + """ + if id_type != 'id' and id_type != 'passport': + raise RootIdentificationException("identification id_type was neither 'id' nor 'passport'") + assert len(country_code) == 2 + return {'type': id_type, + 'number': id_num, + 'country': country_code} + + def details(self): + return self._details + + +class Beneficiary(Person): + def __init__(self, percentage=0, *args, **kwargs): + super().__init__(*args, **kwargs) + assert 0 <= percentage <= 100 + self._details['percentage'] = percentage + + +class Gadgets(Resource): + def __init__(self, client): + super().__init__(client, "get", "modules/root_gadgets/models") + + def list_models(self): + """ List the models available in the root_gadgets module + :return: list of models available, each model is in dict format with keys 'make','name','value' + """ + return self.call() + + def list_phone_brands(self): + models = self.list_models() + return set([phone['make'] for phone in models]) + + def list_phones_by_brand(self, brand): + models = self.list_models() + return set([phone['name'] for phone in models if phone['make'] == brand]) + + def get_phone_value(self, phone): + models = self.list_models() + return list(filter(lambda p: p['name'] == phone, models))[0]['value'] / 100 + + +class Quotes(Resource): + def __init__(self, client): + super().__init__(client, "post", "quotes") + self.gadget_create = self.module_create("root_gadgets") + self.funeral_create = self.module_create("root_funeral") + self.term_create = self.module_create("root_term") + + def module_create(self, module_id=None): + def create(opts): + nonlocal module_id + data = {} + if module_id is None: + module_id = opts["type"] + if module_id == "root_gadgets": + data = self.gadget_data(opts) + elif module_id == "root_term": + data = self.term_data(opts) + elif module_id == "root_funeral": + data = self.funeral_data(opts) + else: + raise Exception("invalid quote type") + return self.call(json=data) + + return create + + @staticmethod + def gadget_data(opts): + return { + "type": "root_gadgets", + "model_name": opts["model_name"] + } + + @staticmethod + def term_data(opts): + return { + "type": "root_term", + "cover_amount": opts["cover_amount"], + "cover_period": opts["cover_period"], + "education_status": opts["education_status"], + "smoker": opts["smoker"], + "gender": opts["gender"], + "age": opts["age"], + "basic_income_per_month": opts["basic_income_per_month"], + } + + @staticmethod + def funeral_data(opts): + return { + "type": "root_funeral", + "cover_amount": opts["cover_amount"], + "has_spouse": opts["has_spouse"], + "number_of_children": opts["number_of_children"], + "extended_family_ages": opts["extended_family_ages"] # integer list + } class Applications(Resource): def __init__(self, client): - super().__init__(client) - + super().__init__(client, "post", "applications") + def create(self, policyholder_id, quote_package_id, monthly_premium, serial_number=None): data = { - "policyholder_id": policyholder_id, + "policyholder_id": policyholder_id, "quote_package_id": quote_package_id, - "monthly_premium": monthly_premium, - "serial_number": serial_number + "monthly_premium": monthly_premium, + "serial_number": serial_number } - return self.call("post", "applications", json=data) + return self.call(json=data) class Claims(Resource): def __init__(self, client): - super().__init__(client) + super().__init__(client, "get", "claims") def list(self, status=None, approval=None): params = {} @@ -69,172 +239,112 @@ def list(self, status=None, approval=None): params = {} if approval: params["approval_status"] = approval - - return self.call("get", "claims", params=params) - def get(self, id): - return self.call("get", f'claims/{id}') + return self.call(params=params) + + def get(self, claim_id): + return self.call('{claim_id}'.format(claim_id=claim_id)) def open(self, policy_id=None, policy_holder_id=None): data = { "policy_id": policy_id, "policy_holder_id": policy_holder_id } - return self.call("post", "claims", json=data) + return self.call("post", json=data) def link_policy(self, claim_id, policy_id): data = { "policy_id": policy_id } - return self.call("post", f'claims/{claim_id}/policy', json=data) + return self.call("post", '{claim_id}/policy'.format(claim_id=claim_id), json=data) def link_policy_holder(self, claim_id, policy_holder_id): data = { "policy_holder_id": policy_holder_id } - return self.call("post", f'claims/{claim_id}/policyholder', json=data) + return self.call("post", '{claim_id}/policyholder'.format(claim_id=claim_id), json=data) def link_events(self, claim_id): - return self.call("post", f'claims/{claim_id}/events') - + return self.call("post", '{claim_id}/events'.format(claim_id=claim_id)) class PolicyHolders(Resource): def __init__(self, client): - super().__init__(client) - - def create(self, id, first_name, last_name, email=None, date_of_birth=None, cellphone=None): - data = { - "id": id, - "first_name": first_name, - "last_name": last_name, - "date_of_birth": date_of_birth, - "email": email, - "cellphone": cellphone - } - return self.call("post", "policyholders", json=data) + super().__init__(client, "get", "policyholders") def list(self): - return self.call("get", "policyholders") + return self.call() + + def get(self, policyholder_id): + return self.call('{policyholder_id}'.format(policyholder_id=policyholder_id)) + + def list_events(self, policyholder_id): + return self.call('{policyholder_id}/events'.format(policyholder_id=policyholder_id)) - def get(self, id): - return self.call("get", f'policyholders/{id}') + def create(self, policyholder: Person): + data = policyholder.details() + return self.call("post", json=data) - def update(self, id, email=None, cellphone=None): + def update(self, policyholder_id, email=None, cellphone=None): data = { - "email": email, + "email": email, "cellphone": cellphone } - return self.call("patch", f'policyholders/{id}', json=data) - - def list_events(self, id): - return self.call("get", f'policyholders/{id}/events') + return self.call("patch", '{policyholder_id}'.format(policyholder_id=policyholder_id), json=data) class Policies(Resource): def __init__(self, client): - super().__init__(client) - - def issue(self, application_id): - data = { - "application_id": application_id, - } - return self.call("post", "policies", json=data) + super().__init__(client, "get", "policies") - def add_beneficiary(self, policy_id, beneficiary_id, first_name, last_name, percentage): - data = { - "id": beneficiary_id, - "first_name": first_name, - "last_name": last_name, - "percentage": percentage - } - return self.call("put", f'policies/{policy_id}/beneficiaries', json=data) - def list(self): - return self.call("get", "policies") + return self.call() def get(self, policy_id): - return self.call("get", f'policies/{policy_id}') + return self.call('{policy_id}'.format(policy_id=policy_id)) - def cancel(self, policy_id, reason): - data = {"reason": reason} - return self.call("post", f'policies/{policy_id}/cancel', json=data) - - def replace(self, policy_id, quote_package_id): - data = {"quote_package_id": quote_package_id} - return self.call("post", f'policies/{policy_id}/replace', json=data) - - def update_billing_amount(self, policy_id, billing_amount): - data = {"billing_amount": billing_amount} - return self.call("post", f'policies/{policy_id}/billing_amount', json=data) + def issue(self, application_id): + data = { + "application_id": application_id, + } + return self.call("post", json=data) def list_beneficiaries(self, policy_id): - return self.call("get", f'policies/{policy_id}/beneficiaries') + return self.call('{policy_id}/beneficiaries'.format(policy_id=policy_id)) def list_events(self, policy_id): - return self.call("get", f'policies/{policy_id}/events') + return self.call('{policy_id}/events'.format(policy_id=policy_id)) + def add_beneficiary(self, policy_id, beneficiaries: [Beneficiary]): + """ -class Quotes(Resource): - def __init__(self, client): - super().__init__(client) - - def create(self, opts): - data = {} - type_ = opts["type"] - if type_ == "root_gadgets": - data = self._gadget_quote(opts) - elif type_ == "root_term": - data = self._term_quote(opts) - elif type_ == "root_funeral": - data = self._funeral_quote(opts) - else: - raise Exception("invalid quote type") - return self.call("post", "quotes", json=data) - - def _gadget_quote(self, opts): - return { - "type": "root_gadgets", - "model_name": opts["model_name"] - } + After a policy has been issued, the beneficiaries that will receive payment on a claim payout can be added. + Updating a policy's beneficiaries replaces any beneficiaries added to the policy in the past. - def _term_quote(self, opts): - return { - "type": "root_term", - "cover_amount": opts["cover_amount"], - "cover_period": opts["cover_period"], - "education_status": opts["education_status"], - "smoker": opts["smoker"], - "gender": opts["gender"], - "age": opts["age"], - "basic_income_per_month": opts["basic_income_per_month"], - } - - def _funeral_quote(self, opts): - return { - "type": "root_funeral", - "cover_amount": opts["cover_amount"], - "has_spouse": opts["has_spouse"], - "number_of_children": opts["number_of_children"], - "extended_family_ages": opts["extended_family_ages"] - } + The number of beneficiaries that can be added and the details required for beneficiaries may differ depending + on the insurance module type of the policy. + When updating beneficiaries, the percentage of a claim payout + that each beneficiary should receive must be provided. The sum of percentages for beneficiaries added must be + 100. -class Gadgets(Resource): - def __init__(self, client): - super().__init__(client) + :param policy_id: + :param beneficiaries: - def list_models(self): - return self.call("get", "gadgets/models") + """ + data = [beneficiary.details() for beneficiary in beneficiaries] + total = sum([datum['percentage'] for datum in data]) + assert total == 100, "Beneficiary percentages must add to 100" + return self.call("put", '{policy_id}/beneficiaries'.format(policy_id=policy_id), json=data) - def list_phone_brands(self): - models = self.list_models() - return set([phone['make'] for phone in models]) + def cancel(self, policy_id, reason): + data = {"reason": reason} + return self.call("post", '{policy_id}/cancel'.format(policy_id=policy_id), json=data) - def list_phones_by_brand(self, brand): - models = self.list_models() - return set([phone['name'] for phone in models if phone['make'] == brand]) + def replace(self, policy_id, quote_package_id): + data = {"quote_package_id": quote_package_id} + return self.call("post", '{policy_id}/replace'.format(policy_id=policy_id), json=data) - def get_phone_value(self, phone): - models = self.list_models() - return list(filter(lambda p: p['name'] == phone, models))[0]['value']/100 + def update_billing_amount(self, policy_id, billing_amount): + data = {"billing_amount": billing_amount} + return self.call("post", '{policy_id}/billing_amount'.format(policy_id=policy_id), json=data) diff --git a/root/main.py b/root/main.py deleted file mode 100644 index 0a419df..0000000 --- a/root/main.py +++ /dev/null @@ -1,10 +0,0 @@ -from insurance import Client -import os - -def main(): - client = Client() - print(client.gadgets.get_phone_value("iPhone 6 Plus 128GB LTE")) - - -if __name__ == "__main__": - main() diff --git a/tests/test_gadgets.py b/tests/test_gadgets.py index ace1a6f..84323aa 100644 --- a/tests/test_gadgets.py +++ b/tests/test_gadgets.py @@ -1,8 +1,8 @@ from context import insurance -import json client = insurance.Client() + def test_list_models(): result = client.gadgets.list_models() assert result @@ -10,6 +10,7 @@ def test_list_models(): assert len(result) > 0 assert any(x for x in result if 'Apple' in x.get('make')) + def test_list_phone_brands(): result = client.gadgets.list_phone_brands() assert result @@ -17,6 +18,7 @@ def test_list_phone_brands(): assert any(x for x in result if 'Apple' in x) assert len(result) > 0 + def test_list_phones_by_brand(): result = client.gadgets.list_phones_by_brand('Apple') assert result @@ -26,7 +28,6 @@ def test_list_phones_by_brand(): def test_get_phone_value(): - #phones = client.gadgets.list_phone_brands() phones = client.gadgets.list_phones_by_brand('Apple') print("Phones") print(type(phones)) From 1a60ae508eeb6b3f446babebce6adc7dc46d3686 Mon Sep 17 00:00:00 2001 From: Christopher Date: Thu, 26 Apr 2018 20:09:41 +0200 Subject: [PATCH 2/4] cleaned up sdk into separate files and with more comments+examples - test_gadgets is now a unittest class - combined listing gadgets and generating a quote for them into single "GadgetsCover" class - made Quotes more OOP for easier subclassing of different modules - Policyholder class as an application of class for type and some validation in python - Resources' call has defaults for sub-classes which can be overridden at call! - main.py has a 'full' process example for gadgets --- README.md | 2 +- main.py | 42 +++++- root/exceptions.py | 16 +- root/insurance.py | 339 +++++------------------------------------- root/policyholder.py | 66 ++++++++ root/resources.py | 232 +++++++++++++++++++++++++++++ tests/context.py | 1 + tests/test_gadgets.py | 60 ++++---- 8 files changed, 422 insertions(+), 336 deletions(-) create mode 100644 root/policyholder.py create mode 100644 root/resources.py diff --git a/README.md b/README.md index be155a0..0ac59cb 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ ROOT_APP_SECRET ```python from root import insurance -client = insurance.Client() +client = insurance.InsuranceClient() phone_brands = client.gadgets.list_phone_brands() ``` diff --git a/main.py b/main.py index 46c917b..b896ae6 100644 --- a/main.py +++ b/main.py @@ -1,11 +1,45 @@ -from root.insurance import Client +from pprint import pprint +from root.insurance import InsuranceClient +from root.policyholder import Policyholder, Beneficiary def main(): - # start insurance client - client = Client() + # 0. Create an insurance client + client = InsuranceClient() phone = "iPhone 6 Plus 128GB LTE" - print(f"The value of a {phone} is R{client.gadgets.get_phone_value(phone)}") + phone_value = client.quotes.get_phone_value(phone) + print("The value of a {} is R{}".format(phone, phone_value)) + # 1. Issue a quote for the thing to be insured. + quotes_data = client.quotes.generate(model_name=phone) + print("The quotes for {} are".format(phone)) + pprint(quotes_data) + + # 2. Create a policy holder + policyholder = Policyholder(Policyholder.identification("id", "6801015800084", "ZA"), "Erlich", "Bachman") + policyholder_data = client.policyholders.create(policyholder=policyholder) + print("Person holding policy is") + pprint(policyholder_data) + # 3. Create an application. For this, a generated quote is required. + chosen_quote = quotes_data[0] + chosen_quote_id = chosen_quote['quote_package_id'] + policyholder_id = policyholder_data['policyholder_id'] + application_data = client.applications.create(policyholder_id=policyholder_id, + quote_package_id=chosen_quote_id, + monthly_premium=chosen_quote['suggested_premium'], + serial_number='random_serial') + print("Application data to insure {} is ".format(phone)) + pprint(application_data) + # 4. Issue policy, if application is approved (this check is not done here) + policy_data = client.policies.issue(application_id=application_data['application_id']) + print("Congratulations, your policy looks as follows:") + pprint(policy_data) + # 5. Add beneficiaries (if supported, which root_gadgets do not) + # updated_policy_data = client.policies.add_beneficiary(policy_data['policy_id'], + # [Beneficiary( + # Policyholder.identification("id", "6801015800084", "ZA"), + # "Erlich", "Bachman", percentage=100.0)]) + # 6. Link credit card + # See API docs https://app.root.co.za/docs/insurance/api#linking-credit-card if __name__ == "__main__": diff --git a/root/exceptions.py b/root/exceptions.py index 2904498..a065075 100644 --- a/root/exceptions.py +++ b/root/exceptions.py @@ -11,12 +11,24 @@ class RootCredentialsException(RootException): """ def __init__(self, message="No ROOT_API_KEY set in environment variables"): - pass + print(message) + class RootIdentificationException(RootException): """Raised when given identification is not syntactically correct Attributes: message -- explanation of why syntax was wrong """ + def __init__(self, message="identification provided was ill-formed"): - pass + print(message) + + +class RootInsufficientDataException(RootException): + """Raised when not enough data is given to a function/method/api call + Attributes: + message -- explanation of why the data was not fully-formed + """ + + def __init__(self, message="not all data fields present"): + print(message) diff --git a/root/insurance.py b/root/insurance.py index 314a18b..f5d0a24 100644 --- a/root/insurance.py +++ b/root/insurance.py @@ -1,23 +1,50 @@ import requests import logging import os -from exceptions import RootCredentialsException, RootIdentificationException +from root.exceptions import RootCredentialsException +from root.policyholder import Policyholder +from root.resources import GadgetCover, Applications, Claims, PolicyHolders, Policies logging.basicConfig(level=logging.DEBUG) -class Client: - def __init__(self, api_key=False): +class InsuranceClient(object): + def __init__(self, api_key=False, cover=GadgetCover): """Provide an interface to access the Root Insurance API. - With an interface, client=Client(), one can get - 1. Quotes (client.quotes), such as for Gadgets (client.gadgets) + With an interface, client=InsuranceClient(), one can get + 1. Quotes (client.quotes), such as for GadgetCover 2. Policy Holders (client.policyholders) 3. Applications (client.applications) 4. Policies (client.policies) - - :param api_key: + To issue a policy follow these steps: + 0. Create an insurance client + >>> client = InsuranceClient() + 1. Issue a quote for the thing to be insured. + >>> quotes_data = client.quotes.generate(model_name="iPhone 6s 64GB LTE") + 2. Create a policy holder + >>> policyholder = Policyholder(Policyholder.identification(...),...) + >>> policyholder_data = client.policyholders.create(policyholder=policyholder) + 3. Create an application. For this, a generated quote is required. + >>> chosen_quote = quotes_data[0] + >>> chosen_quote_id = chosen_quote['quote_package_id'] + >>> policyholder_id = policyholder_data['policyholder_id'] + >>> application_data = client.applications.create(policyholder_id=policyholder_id, + quote_package_id=chosen_quote_id, + monthly_premium=chosen_quote['suggested_premium'], + serial_number='random_serial') + 4. Issue policy, if application is approved (this check is not done here) + >>> policy_data = client.policies.issue(application_id=application_data['application_id']) + 5. Add beneficiaries + >>> updated_policy_data = client.policies.add_beneficiary(policy_data['policy_id'], + [Beneficiary(percentage=100.0,...)]) + 6. Link credit card + See API docs https://app.root.co.za/docs/insurance/api#linking-credit-card + + + :param api_key: for those struggling with ENVIRONMENT VARIABLES, the key can be passed directly + WARNING: do *NOT* make the API_KEY publicly visible """ self.sandbox = os.environ.get('ROOT_SANDBOX', True) self.production = False if self.sandbox else True @@ -28,14 +55,13 @@ def __init__(self, api_key=False): self.claims = Claims(self) self.policyholders = PolicyHolders(self) self.policies = Policies(self) - self.gadgets = Gadgets(self) - self.quotes = Quotes(self) + self.quotes = cover(self) if not self.api_key: raise RootCredentialsException print("[{warning}] Running in production mode: {mode}".format(warning="WARNING" if self.production else "INFO", mode=self.production)) - def call(self, method, path, params=None, **kwargs): + def call(self, method: str, path: str, params: str = None, **kwargs): """Send a request to the Root API. :param method: any method supported by requests lib. :param path: url of api from root.co.za/{version}/insurance/{path} @@ -55,296 +81,3 @@ def call(self, method, path, params=None, **kwargs): logging.error('{} {}'.format(resp.status_code, resp.text)) if self.sandbox: resp.raise_for_status() - - -class Resource(object): - """ - Super class for - """ - - def __init__(self, client, method, path): - self.client = client - self.method = method - self.path = path - - def call(self, method=None, path="", params=None, **kwargs): - if method is None: - method = self.method - full_path = "{}/{}".format(self.path, path) if path != "" else self.path - return self.client.call(method, full_path, params, **kwargs) - - -class Person(object): - """An object for a person's details in the format the Root Insurance API wants - - # create object - >>> person = Person(Person.id_data('id','6801015800084','ZA'),...) - # send in correct format using - >>> data = person.details() - >>> data['extra'] = 'extra details' - >>> Resource.call(json=data) - - """ - - def __init__(self, id_object, first_name, last_name, date_of_birth=None, gender=None, email=None, cellphone=None, - *args, **kwargs): - if not ('type' in id_object and 'number' in id_object and 'country' in id_object): - raise RootIdentificationException() - - self._details = {'id': id_object, - 'first_name': first_name, - 'last_name': last_name} - if date_of_birth is not None: - self._details['date_of_birth'] = date_of_birth - if gender is not None: - self._details['gender'] = gender - if email is not None: - self._details['email'] = email - if cellphone is not None: - self._details['cellphone'] = cellphone - for kw, val in kwargs.items(): - self._details[kw] = val - - @staticmethod - def id_data(id_type: str, id_num: str, country_code: str): - """Helper method to correctly form the 'id' object dict - - Example: - person = Person(Person.id_data('id','6801015800084','ZA'),...) - - API docs: - type string. Either 'id' or 'passport'. - number string. The ID or passport number. - country string. The ISO Alpha-2 country code of the country of the id/passport number. - """ - if id_type != 'id' and id_type != 'passport': - raise RootIdentificationException("identification id_type was neither 'id' nor 'passport'") - assert len(country_code) == 2 - return {'type': id_type, - 'number': id_num, - 'country': country_code} - - def details(self): - return self._details - - -class Beneficiary(Person): - def __init__(self, percentage=0, *args, **kwargs): - super().__init__(*args, **kwargs) - assert 0 <= percentage <= 100 - self._details['percentage'] = percentage - - -class Gadgets(Resource): - def __init__(self, client): - super().__init__(client, "get", "modules/root_gadgets/models") - - def list_models(self): - """ List the models available in the root_gadgets module - :return: list of models available, each model is in dict format with keys 'make','name','value' - """ - return self.call() - - def list_phone_brands(self): - models = self.list_models() - return set([phone['make'] for phone in models]) - - def list_phones_by_brand(self, brand): - models = self.list_models() - return set([phone['name'] for phone in models if phone['make'] == brand]) - - def get_phone_value(self, phone): - models = self.list_models() - return list(filter(lambda p: p['name'] == phone, models))[0]['value'] / 100 - - -class Quotes(Resource): - def __init__(self, client): - super().__init__(client, "post", "quotes") - self.gadget_create = self.module_create("root_gadgets") - self.funeral_create = self.module_create("root_funeral") - self.term_create = self.module_create("root_term") - - def module_create(self, module_id=None): - def create(opts): - nonlocal module_id - data = {} - if module_id is None: - module_id = opts["type"] - if module_id == "root_gadgets": - data = self.gadget_data(opts) - elif module_id == "root_term": - data = self.term_data(opts) - elif module_id == "root_funeral": - data = self.funeral_data(opts) - else: - raise Exception("invalid quote type") - return self.call(json=data) - - return create - - @staticmethod - def gadget_data(opts): - return { - "type": "root_gadgets", - "model_name": opts["model_name"] - } - - @staticmethod - def term_data(opts): - return { - "type": "root_term", - "cover_amount": opts["cover_amount"], - "cover_period": opts["cover_period"], - "education_status": opts["education_status"], - "smoker": opts["smoker"], - "gender": opts["gender"], - "age": opts["age"], - "basic_income_per_month": opts["basic_income_per_month"], - } - - @staticmethod - def funeral_data(opts): - return { - "type": "root_funeral", - "cover_amount": opts["cover_amount"], - "has_spouse": opts["has_spouse"], - "number_of_children": opts["number_of_children"], - "extended_family_ages": opts["extended_family_ages"] # integer list - } - - -class Applications(Resource): - def __init__(self, client): - super().__init__(client, "post", "applications") - - def create(self, policyholder_id, quote_package_id, monthly_premium, serial_number=None): - data = { - "policyholder_id": policyholder_id, - "quote_package_id": quote_package_id, - "monthly_premium": monthly_premium, - "serial_number": serial_number - } - return self.call(json=data) - - -class Claims(Resource): - def __init__(self, client): - super().__init__(client, "get", "claims") - - def list(self, status=None, approval=None): - params = {} - if status: - params["claim_status"] = status - params = {} - if approval: - params["approval_status"] = approval - - return self.call(params=params) - - def get(self, claim_id): - return self.call('{claim_id}'.format(claim_id=claim_id)) - - def open(self, policy_id=None, policy_holder_id=None): - data = { - "policy_id": policy_id, - "policy_holder_id": policy_holder_id - } - return self.call("post", json=data) - - def link_policy(self, claim_id, policy_id): - data = { - "policy_id": policy_id - } - return self.call("post", '{claim_id}/policy'.format(claim_id=claim_id), json=data) - - def link_policy_holder(self, claim_id, policy_holder_id): - data = { - "policy_holder_id": policy_holder_id - } - return self.call("post", '{claim_id}/policyholder'.format(claim_id=claim_id), json=data) - - def link_events(self, claim_id): - return self.call("post", '{claim_id}/events'.format(claim_id=claim_id)) - - -class PolicyHolders(Resource): - def __init__(self, client): - super().__init__(client, "get", "policyholders") - - def list(self): - return self.call() - - def get(self, policyholder_id): - return self.call('{policyholder_id}'.format(policyholder_id=policyholder_id)) - - def list_events(self, policyholder_id): - return self.call('{policyholder_id}/events'.format(policyholder_id=policyholder_id)) - - def create(self, policyholder: Person): - data = policyholder.details() - return self.call("post", json=data) - - def update(self, policyholder_id, email=None, cellphone=None): - data = { - "email": email, - "cellphone": cellphone - } - return self.call("patch", '{policyholder_id}'.format(policyholder_id=policyholder_id), json=data) - - -class Policies(Resource): - def __init__(self, client): - super().__init__(client, "get", "policies") - - def list(self): - return self.call() - - def get(self, policy_id): - return self.call('{policy_id}'.format(policy_id=policy_id)) - - def issue(self, application_id): - data = { - "application_id": application_id, - } - return self.call("post", json=data) - - def list_beneficiaries(self, policy_id): - return self.call('{policy_id}/beneficiaries'.format(policy_id=policy_id)) - - def list_events(self, policy_id): - return self.call('{policy_id}/events'.format(policy_id=policy_id)) - - def add_beneficiary(self, policy_id, beneficiaries: [Beneficiary]): - """ - - After a policy has been issued, the beneficiaries that will receive payment on a claim payout can be added. - Updating a policy's beneficiaries replaces any beneficiaries added to the policy in the past. - - The number of beneficiaries that can be added and the details required for beneficiaries may differ depending - on the insurance module type of the policy. - - When updating beneficiaries, the percentage of a claim payout - that each beneficiary should receive must be provided. The sum of percentages for beneficiaries added must be - 100. - - :param policy_id: - :param beneficiaries: - - """ - data = [beneficiary.details() for beneficiary in beneficiaries] - total = sum([datum['percentage'] for datum in data]) - assert total == 100, "Beneficiary percentages must add to 100" - return self.call("put", '{policy_id}/beneficiaries'.format(policy_id=policy_id), json=data) - - def cancel(self, policy_id, reason): - data = {"reason": reason} - return self.call("post", '{policy_id}/cancel'.format(policy_id=policy_id), json=data) - - def replace(self, policy_id, quote_package_id): - data = {"quote_package_id": quote_package_id} - return self.call("post", '{policy_id}/replace'.format(policy_id=policy_id), json=data) - - def update_billing_amount(self, policy_id, billing_amount): - data = {"billing_amount": billing_amount} - return self.call("post", '{policy_id}/billing_amount'.format(policy_id=policy_id), json=data) diff --git a/root/policyholder.py b/root/policyholder.py new file mode 100644 index 0000000..fd790ae --- /dev/null +++ b/root/policyholder.py @@ -0,0 +1,66 @@ +from root.exceptions import RootIdentificationException + + +class Policyholder(object): + """An object for a person's details in the format the Root Insurance API wants + A person's ID, first name, and last name are always required. + + Example: + + # create object + >>> person = Policyholder(Policyholder.identification('id','6801015800084','ZA'),...) + # send in correct format using + >>> data = person.details() + >>> data['extra'] = 'extra details' + >>> from root.resources import Resource + >>> Resource.call(json=data) + + """ + + def __init__(self, id_object, first_name, last_name, date_of_birth=None, gender=None, email=None, cellphone=None, + *args, **kwargs): + if not ('type' in id_object and 'number' in id_object and 'country' in id_object): + raise RootIdentificationException() + + self._details = {'id': id_object, + 'first_name': first_name, + 'last_name': last_name} + if date_of_birth is not None: + self._details['date_of_birth'] = date_of_birth + if gender is not None: + self._details['gender'] = gender + if email is not None: + self._details['email'] = email + if cellphone is not None: + self._details['cellphone'] = cellphone + for kw, val in kwargs.items(): + self._details[kw] = val + + @staticmethod + def identification(id_type: str, id_num: str, country_code: str): + """Helper method to correctly form the 'id' object dict + + Example: + person = Policyholder(Policyholder.identification('id','6801015800084','ZA'),...) + + API docs: + type string. Either 'id' or 'passport'. + number string. The ID or passport number. + country string. The ISO Alpha-2 country code of the country of the id/passport number. + """ + if id_type != 'id' and id_type != 'passport': + raise RootIdentificationException("identification id_type was neither 'id' nor 'passport'") + assert len(country_code) == 2 + return {'type': id_type, + 'number': id_num, + 'country': country_code} + + def details(self): + return self._details + + +class Beneficiary(Policyholder): + def __init__(self, id_object, first_name, last_name, percentage=0.0, *args, **kwargs): + super().__init__(id_object, first_name, last_name, *args, **kwargs) + assert 0 <= percentage <= 100 + self._details['percentage'] = percentage diff --git a/root/resources.py b/root/resources.py new file mode 100644 index 0000000..39e8e93 --- /dev/null +++ b/root/resources.py @@ -0,0 +1,232 @@ +from root.exceptions import RootInsufficientDataException +from root.policyholder import Policyholder, Beneficiary + + +class Resource(object): + """Super class for resources within the API + Each resource should be its own sub-class which is then instantiated within the main InsuranceClient class, thus + the InsuranceClient remains the interface for the API + Each sub-class can conveniently set a default REST method for API calls, as well as it's hierarchical URL path + There are a number of possible ways to validate data (at UI, in a new class (such as Policyholder), + in a Resource sub-class explicitly, or in sub-class by overriding validate method). + The choice shall be left up to the user depending on requirements. + """ + + def __init__(self, client, method: str = 'get', path: str = ''): + self.client = client + self.method = method + self.path = path + + def call(self, method=None, sub_path="", params=None, path=None, **kwargs): + if method is None: + method = self.method + if path is None: + path = "{}/{}".format(self.path, sub_path) if sub_path != "" else self.path + return self.client.call(method, path, params, **kwargs) + + +class Quotes(Resource): + """Super class for generating quotes of the insurance product. Sub-classes can be bare-bones with just a simple + init method. But can be usefully expanded to have extra useful features for the user, see GadgetCover. + """ + def __init__(self, client, module_id: str, data_fields: list): + super().__init__(client, "post", "quotes") + if "type" not in data_fields: + data_fields.insert(0, "type") + self.data_fields = set(data_fields) + self.module_id = module_id + + def generate(self, data: dict = None, **kwargs) -> dict: + """Generate a quote using either a dictionary or keyword arguments as data + (using both will merge them keyword precedence). + Raises a `RootInsufficientDataException` if not all required fields were provided. E + xtra fields are ignored by the API + :param data: data required for the quote in dictionary form + :param kwargs: data required for the quote in keyword form + :return: A quote in dictionary format + """ + if data is None: + data = {} + for kw, val in kwargs.items(): + data[kw] = val + if "type" not in data: + data["type"] = self.module_id + if self.data_fields < set(data): + raise RootInsufficientDataException() + return self.call(json=data) + + +class GadgetCover(Quotes): + """Cover for smartphones. + All that is required is the model_name, which must be fully-formed to get a response from the API. + Additional methods are provided to list the models available to be insured. + """ + def __init__(self, client): + super().__init__(client, "root_gadgets", ["model_name"]) + + def list_models(self): + """ List the models available in the root_gadgets module + :return: list of models available, each model is in dict format with keys 'make','name','value' + """ + return self.call("get", path="modules/root_gadgets/models") + + def list_phone_brands(self): + models = self.list_models() + return set([phone['make'] for phone in models]) + + def list_phones_by_brand(self, brand): + models = self.list_models() + return set([phone['name'] for phone in models if phone['make'] == brand]) + + def get_phone_value(self, phone): + models = self.list_models() + return list(filter(lambda p: p['name'] == phone, models))[0]['value'] / 100 + + +class FuneralCover(Quotes): + def __init__(self, client): + super().__init__(client, "root_funeral", + ["cover_amount", "has_spouse", "number_of_children", "extended_family_ages"]) + + +class TermCover(Quotes): + def __init__(self, client): + super().__init__(client, "root_term", + ["cover_amount", "cover_period", "education_status", "smoker", "gender", "age", + "basic_income_per_month"]) + + +class Applications(Resource): + def __init__(self, client): + super().__init__(client, "post", "applications") + + def create(self, policyholder_id, quote_package_id, monthly_premium, serial_number=None): + data = { + "policyholder_id": policyholder_id, + "quote_package_id": quote_package_id, + "monthly_premium": monthly_premium, + "serial_number": serial_number + } + return self.call(json=data) + + +class Claims(Resource): + def __init__(self, client): + super().__init__(client, "get", "claims") + + def list(self, status=None, approval=None): + params = {} + if status: + params["claim_status"] = status + params = {} + if approval: + params["approval_status"] = approval + + return self.call(params=params) + + def get(self, claim_id): + return self.call('{claim_id}'.format(claim_id=claim_id)) + + def open(self, policy_id=None, policy_holder_id=None): + data = { + "policy_id": policy_id, + "policy_holder_id": policy_holder_id + } + return self.call("post", json=data) + + def link_policy(self, claim_id, policy_id): + data = { + "policy_id": policy_id + } + return self.call("post", '{claim_id}/policy'.format(claim_id=claim_id), json=data) + + def link_policy_holder(self, claim_id, policy_holder_id): + data = { + "policy_holder_id": policy_holder_id + } + return self.call("post", '{claim_id}/policyholder'.format(claim_id=claim_id), json=data) + + def link_events(self, claim_id): + return self.call("post", '{claim_id}/events'.format(claim_id=claim_id)) + + +class PolicyHolders(Resource): + def __init__(self, client): + super().__init__(client, "get", "policyholders") + + def list(self): + return self.call() + + def get(self, policyholder_id): + return self.call('{policyholder_id}'.format(policyholder_id=policyholder_id)) + + def list_events(self, policyholder_id): + return self.call('{policyholder_id}/events'.format(policyholder_id=policyholder_id)) + + def create(self, policyholder: Policyholder): + data = policyholder.details() + return self.call("post", json=data) + + def update(self, policyholder_id, email=None, cellphone=None): + data = { + "email": email, + "cellphone": cellphone + } + return self.call("patch", '{policyholder_id}'.format(policyholder_id=policyholder_id), json=data) + + +class Policies(Resource): + def __init__(self, client): + super().__init__(client, "get", "policies") + + def list(self): + return self.call() + + def get(self, policy_id): + return self.call('{policy_id}'.format(policy_id=policy_id)) + + def issue(self, application_id): + data = { + "application_id": application_id, + } + return self.call("post", json=data) + + def list_beneficiaries(self, policy_id): + return self.call('{policy_id}/beneficiaries'.format(policy_id=policy_id)) + + def list_events(self, policy_id): + return self.call('{policy_id}/events'.format(policy_id=policy_id)) + + def add_beneficiary(self, policy_id, beneficiaries: [Beneficiary]): + """ + + After a policy has been issued, the beneficiaries that will receive payment on a claim payout can be added. + Updating a policy's beneficiaries replaces any beneficiaries added to the policy in the past. + + The number of beneficiaries that can be added and the details required for beneficiaries may differ depending + on the insurance module type of the policy. + + When updating beneficiaries, the percentage of a claim payout + that each beneficiary should receive must be provided. The sum of percentages for beneficiaries added must be + 100. + + :param policy_id: + :param beneficiaries: + + """ + data = [beneficiary.details() for beneficiary in beneficiaries] + total = sum([datum['percentage'] for datum in data]) + assert total == 100, "Beneficiary percentages must add to 100" + return self.call("put", '{policy_id}/beneficiaries'.format(policy_id=policy_id), json=data) + + def cancel(self, policy_id, reason): + data = {"reason": reason} + return self.call("post", '{policy_id}/cancel'.format(policy_id=policy_id), json=data) + + def replace(self, policy_id, quote_package_id): + data = {"quote_package_id": quote_package_id} + return self.call("post", '{policy_id}/replace'.format(policy_id=policy_id), json=data) + + def update_billing_amount(self, policy_id, billing_amount): + data = {"billing_amount": billing_amount} + return self.call("post", '{policy_id}/billing_amount'.format(policy_id=policy_id), json=data) diff --git a/tests/context.py b/tests/context.py index 9d67816..1c42ba9 100644 --- a/tests/context.py +++ b/tests/context.py @@ -3,3 +3,4 @@ sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) from root import insurance +from root.insurance import InsuranceClient diff --git a/tests/test_gadgets.py b/tests/test_gadgets.py index 84323aa..e5abb83 100644 --- a/tests/test_gadgets.py +++ b/tests/test_gadgets.py @@ -1,35 +1,43 @@ -from context import insurance +import unittest -client = insurance.Client() +from root.resources import GadgetCover +from tests.context import insurance -def test_list_models(): - result = client.gadgets.list_models() - assert result - assert result.__len__() - assert len(result) > 0 - assert any(x for x in result if 'Apple' in x.get('make')) +class GadgetCoverTestCase(unittest.TestCase): + def setUp(self): + self.client = insurance.InsuranceClient(cover=GadgetCover) -def test_list_phone_brands(): - result = client.gadgets.list_phone_brands() - assert result - assert result.__len__() - assert any(x for x in result if 'Apple' in x) - assert len(result) > 0 + def test_list_models(self): + result = self.client.quotes.list_models() + assert result + assert result.__len__() + assert len(result) > 0 + assert any(x for x in result if 'Apple' in x.get('make')) + def test_list_phone_brands(self): + result = self.client.quotes.list_phone_brands() + assert result + assert result.__len__() + assert any(x for x in result if 'Apple' in x) + assert len(result) > 0 -def test_list_phones_by_brand(): - result = client.gadgets.list_phones_by_brand('Apple') - assert result - assert result.__len__() - assert len(result) > 0 - assert any(x for x in result if 'iPhone' in x) + def test_list_phones_by_brand(self): + result = self.client.quotes.list_phones_by_brand('Apple') + assert result + assert result.__len__() + assert len(result) > 0 + assert any(x for x in result if 'iPhone' in x) + def test_get_phone_value(self): + phones = self.client.quotes.list_phones_by_brand('Apple') + print("Phones") + print(type(phones)) + result = self.client.quotes.get_phone_value(list(phones)[0]) + assert int(result) > 0 + + def test_get_phone_value_unknown_model(self): + with self.assertRaises(IndexError) as context: + self.client.quotes.get_phone_value("iPhone 600 Plus 128TB LTD") -def test_get_phone_value(): - phones = client.gadgets.list_phones_by_brand('Apple') - print("Phones") - print(type(phones)) - result = client.gadgets.get_phone_value(list(phones)[0]) - assert int(result) > 0 From 530364a686f149bf6666ce8dc524f0034dfa48d9 Mon Sep 17 00:00:00 2001 From: Christopher Date: Thu, 26 Apr 2018 20:24:20 +0200 Subject: [PATCH 3/4] small Readme updated --- README.md | 5 +++-- main.py | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 0ac59cb..0ce4805 100644 --- a/README.md +++ b/README.md @@ -41,9 +41,10 @@ ROOT_APP_SECRET ## Code ```python -from root import insurance +from root.insurance import InsuranceClient +from root.resources import GadgetCover -client = insurance.InsuranceClient() +client = InsuranceClient(cover=GadgetCover) phone_brands = client.gadgets.list_phone_brands() ``` diff --git a/main.py b/main.py index b896ae6..7cd4b7e 100644 --- a/main.py +++ b/main.py @@ -1,11 +1,12 @@ from pprint import pprint from root.insurance import InsuranceClient from root.policyholder import Policyholder, Beneficiary +from root.resources import GadgetCover def main(): # 0. Create an insurance client - client = InsuranceClient() + client = InsuranceClient(cover=GadgetCover) phone = "iPhone 6 Plus 128GB LTE" phone_value = client.quotes.get_phone_value(phone) print("The value of a {} is R{}".format(phone, phone_value)) From 328ae728eb3885d1b752a7b6996b17c29cb7c5f3 Mon Sep 17 00:00:00 2001 From: Christopher Date: Sat, 28 Apr 2018 10:29:10 +0200 Subject: [PATCH 4/4] adjustments for better use as a module - put everything in 'insurance' s ubmodule so imports are all through 'root.insurance' --- README.md | 8 +++----- main.py => example.py | 4 +--- root/insurance/__init__.py | 3 +++ root/{ => insurance}/exceptions.py | 0 root/{ => insurance}/insurance.py | 6 +++--- root/{ => insurance}/policyholder.py | 2 +- root/{ => insurance}/resources.py | 4 ++-- setup.py | 6 +++--- tests/__init__.py | 0 tests/context.py | 7 ++++++- tests/test_gadgets.py | 8 +++----- 11 files changed, 25 insertions(+), 23 deletions(-) rename main.py => example.py (94%) create mode 100644 root/insurance/__init__.py rename root/{ => insurance}/exceptions.py (100%) rename root/{ => insurance}/insurance.py (95%) rename root/{ => insurance}/policyholder.py (97%) rename root/{ => insurance}/resources.py (98%) create mode 100644 tests/__init__.py diff --git a/README.md b/README.md index 0ce4805..49f7ecf 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ Root’s developers and our community are expected to abide by the [Contributor ``` pip install rootsdk ``` -or +or for active development ``` git clone git@github.com:root-community/root-insurance-python.git pip install -e root-insurance-python @@ -34,15 +34,13 @@ pip install -e root-insurance-python ## Environment Variables ``` -ROOT_APP_ID -ROOT_APP_SECRET +ROOT_API_KEY ``` ## Code ```python -from root.insurance import InsuranceClient -from root.resources import GadgetCover +from root.insurance import InsuranceClient,GadgetCover client = InsuranceClient(cover=GadgetCover) phone_brands = client.gadgets.list_phone_brands() diff --git a/main.py b/example.py similarity index 94% rename from main.py rename to example.py index 7cd4b7e..d4c79fd 100644 --- a/main.py +++ b/example.py @@ -1,7 +1,5 @@ from pprint import pprint -from root.insurance import InsuranceClient -from root.policyholder import Policyholder, Beneficiary -from root.resources import GadgetCover +from root.insurance import InsuranceClient, GadgetCover, Policyholder, Beneficiary def main(): diff --git a/root/insurance/__init__.py b/root/insurance/__init__.py new file mode 100644 index 0000000..71f585b --- /dev/null +++ b/root/insurance/__init__.py @@ -0,0 +1,3 @@ +from root.insurance.insurance import InsuranceClient +from root.insurance.policyholder import Policyholder, Beneficiary +from root.insurance.resources import GadgetCover, TermCover, FuneralCover diff --git a/root/exceptions.py b/root/insurance/exceptions.py similarity index 100% rename from root/exceptions.py rename to root/insurance/exceptions.py diff --git a/root/insurance.py b/root/insurance/insurance.py similarity index 95% rename from root/insurance.py rename to root/insurance/insurance.py index f5d0a24..6916e21 100644 --- a/root/insurance.py +++ b/root/insurance/insurance.py @@ -1,9 +1,9 @@ import requests import logging import os -from root.exceptions import RootCredentialsException -from root.policyholder import Policyholder -from root.resources import GadgetCover, Applications, Claims, PolicyHolders, Policies +from root.insurance.exceptions import RootCredentialsException +from root.insurance.policyholder import Policyholder +from root.insurance.resources import GadgetCover, Applications, Claims, PolicyHolders, Policies logging.basicConfig(level=logging.DEBUG) diff --git a/root/policyholder.py b/root/insurance/policyholder.py similarity index 97% rename from root/policyholder.py rename to root/insurance/policyholder.py index fd790ae..5c8f737 100644 --- a/root/policyholder.py +++ b/root/insurance/policyholder.py @@ -1,4 +1,4 @@ -from root.exceptions import RootIdentificationException +from root.insurance.exceptions import RootIdentificationException class Policyholder(object): diff --git a/root/resources.py b/root/insurance/resources.py similarity index 98% rename from root/resources.py rename to root/insurance/resources.py index 39e8e93..64c4c8b 100644 --- a/root/resources.py +++ b/root/insurance/resources.py @@ -1,5 +1,5 @@ -from root.exceptions import RootInsufficientDataException -from root.policyholder import Policyholder, Beneficiary +from root.insurance.exceptions import RootInsufficientDataException +from root.insurance.policyholder import Policyholder, Beneficiary class Resource(object): diff --git a/setup.py b/setup.py index 2915312..3a6227a 100644 --- a/setup.py +++ b/setup.py @@ -35,12 +35,12 @@ # For a discussion on single-sourcing the version across setup.py and the # project code, see # https://packaging.python.org/en/latest/single_source_version.html - version='0.1.3', # Required + version='0.2.0', # Required # This is a one-line description or tagline of what your project does. This # corresponds to the "Summary" metadata field: # https://packaging.python.org/specifications/core-metadata/#summary - description='A python SDK for the Root insurance API', # Required + description='A python SDK for the Root Insurance API', # Required # This should be a valid link to your project's main homepage. # @@ -50,7 +50,7 @@ # This should be your name or the name of the organization which owns the # project. - author='Brendan Ball', # Optional + author='Root Community, Brendan Ball, Christopher Currin', # Optional # Classifiers help users find your project by categorizing it. # diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/context.py b/tests/context.py index 1c42ba9..3ec8b29 100644 --- a/tests/context.py +++ b/tests/context.py @@ -1,6 +1,11 @@ +# +# Import this file at the top of every test to have 'root' in context +# e.g. +# >>> import context # import of 'root' won't work without this +# >>> from root.insurance import InsuranceClient +# import os import sys sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) from root import insurance -from root.insurance import InsuranceClient diff --git a/tests/test_gadgets.py b/tests/test_gadgets.py index e5abb83..6714dde 100644 --- a/tests/test_gadgets.py +++ b/tests/test_gadgets.py @@ -1,13 +1,11 @@ import unittest - -from root.resources import GadgetCover -from tests.context import insurance - +import context +from root.insurance import InsuranceClient, GadgetCover class GadgetCoverTestCase(unittest.TestCase): def setUp(self): - self.client = insurance.InsuranceClient(cover=GadgetCover) + self.client = InsuranceClient(cover=GadgetCover) def test_list_models(self): result = self.client.quotes.list_models()