From 0270fc0701058dd1c5fb3f4aa16a5768be611ed5 Mon Sep 17 00:00:00 2001 From: tommycbird Date: Fri, 2 Jun 2023 14:57:12 -0400 Subject: [PATCH 1/5] Added testing for and modified clean_dict --- route4me/advanced_constraint.py | 19 +------------------ route4me/utils.py | 6 +++--- tests/test_utils.py | 26 ++++++++++++++++++++++++++ 3 files changed, 30 insertions(+), 21 deletions(-) diff --git a/route4me/advanced_constraint.py b/route4me/advanced_constraint.py index 6bb135f..c74870e 100644 --- a/route4me/advanced_constraint.py +++ b/route4me/advanced_constraint.py @@ -20,24 +20,7 @@ def __init__(self): "lat": None, "lng": None, } - self.location_sequence_pattern = [ - "", - { - "alias": None, - "address": None, - "lat": None, - "lng": None, - } - ] - self.location_sequence_pattern = [ - "", - { - "alias": None, - "address": None, - "lat": None, - "lng": None, - } - ] + self.location_sequence_pattern = [] self.group = None def to_dict(self): diff --git a/route4me/utils.py b/route4me/utils.py index c130b20..bec815c 100644 --- a/route4me/utils.py +++ b/route4me/utils.py @@ -38,8 +38,8 @@ def clean_dict(data): cleaned_list = [] for i in data: cleaned_item = clean_dict(i) - # Append non-None items - if cleaned_item not in [None, ""]: + # Append non-None items except empty strings + if cleaned_item or cleaned_item == "": cleaned_list.append(cleaned_item) return cleaned_list if cleaned_list else None elif isinstance(data, dict): @@ -47,7 +47,7 @@ def clean_dict(data): for k, v in data.items(): cleaned_v = clean_dict(v) # Append non-None key-value pairs - if cleaned_v not in [None, ""]: + if cleaned_v: cleaned_dict[k] = cleaned_v return cleaned_dict if cleaned_dict else None else: diff --git a/tests/test_utils.py b/tests/test_utils.py index 5e3a0ea..c140847 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -2,6 +2,7 @@ import unittest from route4me.utils import json2obj +from route4me.utils import clean_dict from tests.base import Route4MeAPITestSuite @@ -15,6 +16,31 @@ def test_json2obj(self): json_obj = json2obj(json_data) self.assertTrue(hasattr(json_obj, 'variable1')) + def test_clean_dict(self): + # None and "" values + input_dict = {"key1": "value1", "key2": None, "key3": "", "key4": ["", None, "value4"]} + cleaned_dict = clean_dict(input_dict) + expected_dict = {"key1": "value1", "key4": ["", "value4"]} + self.assertEqual(cleaned_dict, expected_dict) + + # empty dict and list values + input_dict = {"key1": "value1", "key2": {}, "key3": []} + cleaned_dict = clean_dict(input_dict) + expected_dict = {"key1": "value1"} + self.assertEqual(cleaned_dict, expected_dict) + + # None and "" values + input_list = ["value1", None, "", ["", None, "value2"]] + cleaned_list = clean_dict(input_list) + expected_list = ["value1", "", ["", "value2"]] + self.assertEqual(cleaned_list, expected_list) + + # empty dict and list values + input_list = ["value1", {}, []] + cleaned_list = clean_dict(input_list) + expected_list = ["value1"] + self.assertEqual(cleaned_list, expected_list) + if __name__ == '__main__': unittest.main() From 6283680d6e445d90e9ee1c7d98921466a599d804 Mon Sep 17 00:00:00 2001 From: tommycbird Date: Thu, 27 Jul 2023 10:29:10 -0700 Subject: [PATCH 2/5] make_request change and misc bug fixes --- examples/.DS_Store | Bin 0 -> 10244 bytes examples/addresses/add_route_destinations.py | 14 +- ...ute_destinations_into_specific_position.py | 73 +++--- .../addresses/move_destination_to_route.py | 15 +- examples/rapid_address/get_street_data.py | 13 +- .../get_street_data_get_single.py | 9 +- .../get_street_data_offset_limit.py | 13 +- .../rapid_address/get_street_data_service.py | 11 +- .../get_street_data_service_offset_limit.py | 11 +- examples/rapid_address/get_street_data_zip.py | 11 +- .../get_street_data_zip_offset_limit.py | 11 +- examples/routes/duplicate_route.py | 17 +- examples/routes/resequence_multiple_stops.py | 11 +- examples/routes/update_route.py | 20 +- examples/routes/update_route_parameters.py | 23 +- route4me/activity_feed.py | 62 +++-- route4me/address.py | 84 ++++--- route4me/address_book.py | 58 +++-- route4me/api.py | 10 +- route4me/avoidance_zones.py | 56 +++-- route4me/base.py | 5 +- route4me/exceptions.py | 12 + route4me/file_uploading.py | 36 ++- route4me/gps.py | 22 +- route4me/members.py | 123 ++++++---- route4me/optimization.py | 60 +++-- route4me/orders.py | 64 +++-- route4me/rapid_address.py | 29 ++- route4me/route.py | 229 +++++++++++------- route4me/route_status.py | 43 +++- route4me/telematics.py | 116 ++++++--- route4me/territory.py | 56 +++-- route4me/utils.py | 14 ++ route4me/vehicles.py | 14 +- 34 files changed, 880 insertions(+), 465 deletions(-) create mode 100644 examples/.DS_Store diff --git a/examples/.DS_Store b/examples/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..31b050f13dca1b93a917ca05cae25046b764da67 GIT binary patch literal 10244 zcmeHMO>Z1U5Up|Scs3gwD@r6Jgct-82#nbz1}VyAG0uSlmuv(FJ~q2MV|(QF%xdWTh{sVu3E5CvxKZ83bc&}zQp6;1IVh}P?_iDPvv-P^FURT#_w?)L;n})YV0uk|X zQEpvFSJU`CuS#1fp1cX^0WT8EM7sFw$f%<29ykUZ1C9a5fMdWha2XiDJDbgK=F;_! z0mp!2;3)&F4*@R9b|DwJ)Y5@Yw*ZiB#{5{a9v-izSk=h55IVzeuuv5bz27awO-0PmCx{My!NUP8C9pYx^&VnPaA!wKc780e^Aa>? zz{MD0IaXJ`)sZS*2ze@_eZ)6~olu5q@6d{q?3DF4!D0;E2;NR{_A-PW@ z>(HDcDrOs_W1J!S5#r>DQ{A_WW~QU_G^!>7!@_&DV?3{gu6o9Zx-wVRLwGi?Gr6JGa!WBXyr))m z#h6*kEH{vAKxR6RnW@RuZ)k2J9_Hq;W;f?PQ>!WR{=mXA64O7fTH?8ujVfXxQ40)Y zLmtWlWbVFv3b_L&ttzEi{acBP>+083dCO;lY)mWFf=rZgS4%i+Ehrby)a}p5qoPLt z+#e0&@ub!I)myo;x^{KlU-z5-SDmAD-kJ0!)A3#}`+|QxO4DI~dEe`Q7Dw~$*3FO8 zWYUY1C{sWjMo{_k^Ee69`Cd9r!m(mI`4@h}Z*;d_K0W=QwYweMy|;U|9h}~~)7lPp z?|yi8*6?q=@&5gX-Q(d&l75eRgXVd;l#N0z1?!O!|E!ZG!!$X>b7EdYCTku0*VcWR z&37+6`irivvemc#!YVU6ul=;ZDmFsiK3yrguKUR2G1g{SS7DfyD_@tg@|B9U^V--g zGLP??b9;3a6Z2$@T4nCUW) zoME$?fz?P^DZ6r8TIkO!aNX#rGRElF;8&!%eF7%vCmjQh0mp!2;PNwYC3i7sLOyu* z|Nk$4AkIU_fMejo40x;io&7z)ms>P@B%ZYgxE|wTquwHy3PGpG@sN5Pk3V=E|28h_ kwkX+f-!9}Lmw1BqpZ_zUI7M{(|3_~Bf6>#0|M~v^Pu8S+3IG5A literal 0 HcmV?d00001 diff --git a/examples/addresses/add_route_destinations.py b/examples/addresses/add_route_destinations.py index 69d0337..5d64621 100644 --- a/examples/addresses/add_route_destinations.py +++ b/examples/addresses/add_route_destinations.py @@ -112,11 +112,15 @@ def main(api_key): print('Inserting addresses in Route: {}'.format(route_id)) response = r4m.route.insert_address_into_route(addresses, route_id) - print('Addresses after insert') - for i, address in enumerate(response['addresses']): - print('Number {}:'.format(i)) - print('\taddress: {}'.format(address['address'])) - print('\t') + + if isinstance(response, dict) and 'errors' in response.keys(): + print('. '.join(response['errors'])) + else: + print('Addresses after insert') + for i, address in enumerate(response['addresses']): + print('Number {}:'.format(i)) + print('\taddress: {}'.format(address['address'])) + print('\t') # codebeat:enable[LOC, ABC] diff --git a/examples/addresses/add_route_destinations_into_specific_position.py b/examples/addresses/add_route_destinations_into_specific_position.py index 700b074..d4b02c1 100644 --- a/examples/addresses/add_route_destinations_into_specific_position.py +++ b/examples/addresses/add_route_destinations_into_specific_position.py @@ -83,42 +83,49 @@ def main(api_key): response = r4m.run_optimization() - print('Current Addresses') - for i, address in enumerate(response['addresses']): - print('Number {}:'.format(i)) - print('\taddress: {}'.format(address['address'])) - print('\t') - addresses = { - 'addresses': [ - { - 'address': '555 W 57th St New York, NY 10019', - 'lat': 40.7718005, - 'lng': -73.9897716, - 'alias': 'BMW of Manhattan', - 'sequence_no': 2, - 'time': 300, - }, - { - 'address': '57 W 57th St New York, NY 10019', - 'lat': 40.7558695, - 'lng': -73.9862019, - 'alias': 'Verizon Wireless', - 'sequence_no': 5, - 'time': 300, - } - ] - } + if isinstance(response, dict) and 'errors' in response.keys(): + print('. '.join(response['errors'])) + else: + print('Current Addresses') + for i, address in enumerate(response['addresses']): + print('Number {}:'.format(i)) + print('\taddress: {}'.format(address['address'])) + print('\t') + addresses = { + 'addresses': [ + { + 'address': '555 W 57th St New York, NY 10019', + 'lat': 40.7718005, + 'lng': -73.9897716, + 'alias': 'BMW of Manhattan', + 'sequence_no': 2, + 'time': 300, + }, + { + 'address': '57 W 57th St New York, NY 10019', + 'lat': 40.7558695, + 'lng': -73.9862019, + 'alias': 'Verizon Wireless', + 'sequence_no': 5, + 'time': 300, + } + ] + } - route_id = response['addresses'][1]['route_id'] + route_id = response['addresses'][1]['route_id'] - print('Inserting addresses in Route: {}'.format(route_id)) + print('Inserting addresses in Route: {}'.format(route_id)) - response = r4m.route.insert_address_into_route(addresses, route_id) - print('Addresses after insert') - for i, address in enumerate(response['addresses']): - print('Number {}:'.format(i)) - print('\taddress: {}'.format(address['address'])) - print('\t') + response = r4m.route.insert_address_into_route(addresses, route_id) + + if isinstance(response, dict) and 'errors' in response.keys(): + print('. '.join(response['errors'])) + else: + print('Addresses after insert') + for i, address in enumerate(response['addresses']): + print('Number {}:'.format(i)) + print('\taddress: {}'.format(address['address'])) + print('\t') # codebeat:enable[LOC, ABC] diff --git a/examples/addresses/move_destination_to_route.py b/examples/addresses/move_destination_to_route.py index b161d89..9f89c55 100644 --- a/examples/addresses/move_destination_to_route.py +++ b/examples/addresses/move_destination_to_route.py @@ -127,12 +127,15 @@ def main(api_key): response = r4m.route.move_addresses_from_route(addresses, route_id) print('Addresses after move') - for i, address in enumerate(response['addresses']): - print('Number {}:'.format(i)) - if address['is_depot']: - print('\t This is a Depot') - print('\taddress: {}'.format(address['address'])) - print('\t') + if isinstance(response, dict) and 'errors' in response.keys(): + print('. '.join(response['errors'])) + else: + for i, address in enumerate(response['addresses']): + print('Number {}:'.format(i)) + if address['is_depot']: + print('\t This is a Depot') + print('\taddress: {}'.format(address['address'])) + print('\t') # codebeat:enable[LOC, ABC] diff --git a/examples/rapid_address/get_street_data.py b/examples/rapid_address/get_street_data.py index 69d8912..f56994a 100644 --- a/examples/rapid_address/get_street_data.py +++ b/examples/rapid_address/get_street_data.py @@ -10,10 +10,15 @@ def main(api_key): rapid_address = route4me.rapid_address response = rapid_address.get_street_data() - for street in response: - print('Street Name:\t{0}\t\tZip Code:\t{1}'.format( - street.get('street_name'), - street.get('zipcode'))) + if isinstance(response, dict) and 'errors' in response.keys(): + print('. '.join(response['errors'])) + elif not response: + print("Unknown error occured") + else: + for street in response: + print('Street Name:\t{0}\t\tZip Code:\t{1}'.format( + street.get('street_name'), + street.get('zipcode'))) if __name__ == '__main__': diff --git a/examples/rapid_address/get_street_data_get_single.py b/examples/rapid_address/get_street_data_get_single.py index 0174196..fd354d6 100644 --- a/examples/rapid_address/get_street_data_get_single.py +++ b/examples/rapid_address/get_street_data_get_single.py @@ -9,9 +9,12 @@ def main(api_key): rapid_address = route4me.rapid_address response = rapid_address.get_street_data(pk=33) - print('Street Name:\t{0}\t\tZip Code:\t{1}'.format( - response.get('street_name'), - response.get('zipcode'))) + if isinstance(response, dict) and 'errors' in response.keys(): + print('. '.join(response['errors'])) + else: + print('Street Name:\t{0}\t\tZip Code:\t{1}'.format( + response.get('street_name'), + response.get('zipcode'))) if __name__ == '__main__': diff --git a/examples/rapid_address/get_street_data_offset_limit.py b/examples/rapid_address/get_street_data_offset_limit.py index b10b646..dd38a98 100644 --- a/examples/rapid_address/get_street_data_offset_limit.py +++ b/examples/rapid_address/get_street_data_offset_limit.py @@ -10,10 +10,15 @@ def main(api_key): rapid_address = route4me.rapid_address response = rapid_address.get_street_data(offset=10, limit=10) - for street in response: - print('Street Name:\t{0}\t\tZip Code:\t{1}'.format( - street.get('street_name'), - street.get('zipcode'))) + if isinstance(response, dict) and 'errors' in response.keys(): + print('. '.join(response['errors'])) + elif not response: + print("Unknown error occured") + else: + for street in response: + print('Street Name:\t{0}\t\tZip Code:\t{1}'.format( + street.get('street_name'), + street.get('zipcode'))) if __name__ == '__main__': diff --git a/examples/rapid_address/get_street_data_service.py b/examples/rapid_address/get_street_data_service.py index be4e405..2328c6e 100644 --- a/examples/rapid_address/get_street_data_service.py +++ b/examples/rapid_address/get_street_data_service.py @@ -11,10 +11,13 @@ def main(api_key): rapid_address = route4me.rapid_address response = rapid_address.get_street_data_service(housenumber=1800, zipcode=33166) - for street in response: - print('Street Name:\t{0}\t\tZip Code:\t{1}'.format( - street.get('street_name'), - street.get('zipcode'))) + if isinstance(response, dict) and 'errors' in response.keys(): + print('. '.join(response['errors'])) + else: + for street in response: + print('Street Name:\t{0}\t\tZip Code:\t{1}'.format( + street.get('street_name'), + street.get('zipcode'))) if __name__ == '__main__': diff --git a/examples/rapid_address/get_street_data_service_offset_limit.py b/examples/rapid_address/get_street_data_service_offset_limit.py index 2e5d87d..04f03ae 100644 --- a/examples/rapid_address/get_street_data_service_offset_limit.py +++ b/examples/rapid_address/get_street_data_service_offset_limit.py @@ -13,10 +13,13 @@ def main(api_key): zipcode=33166, offset=10, limit=5) - for street in response: - print('Street Name:\t{0}\t\tZip Code:\t{1}'.format( - street.get('street_name'), - street.get('zipcode'))) + if isinstance(response, dict) and 'errors' in response.keys(): + print('. '.join(response['errors'])) + else: + for street in response: + print('Street Name:\t{0}\t\tZip Code:\t{1}'.format( + street.get('street_name'), + street.get('zipcode'))) if __name__ == '__main__': diff --git a/examples/rapid_address/get_street_data_zip.py b/examples/rapid_address/get_street_data_zip.py index 3b31b11..faa50b7 100644 --- a/examples/rapid_address/get_street_data_zip.py +++ b/examples/rapid_address/get_street_data_zip.py @@ -9,10 +9,13 @@ def main(api_key): rapid_address = route4me.rapid_address response = rapid_address.get_street_data_zip(zipcode=33166) - for street in response: - print('Street Name:\t{0}\t\tZip Code:\t{1}'.format( - street.get('street_name'), - street.get('zipcode'))) + if isinstance(response, dict) and 'errors' in response.keys(): + print('. '.join(response['errors'])) + else: + for street in response: + print('Street Name:\t{0}\t\tZip Code:\t{1}'.format( + street.get('street_name'), + street.get('zipcode'))) if __name__ == '__main__': diff --git a/examples/rapid_address/get_street_data_zip_offset_limit.py b/examples/rapid_address/get_street_data_zip_offset_limit.py index f2474e0..fcfbc05 100644 --- a/examples/rapid_address/get_street_data_zip_offset_limit.py +++ b/examples/rapid_address/get_street_data_zip_offset_limit.py @@ -12,10 +12,13 @@ def main(api_key): response = rapid_address.get_street_data_zip(zipcode=33166, offset=10, limit=20) - for street in response: - print('Street Name:\t{0}\t\tZip Code:\t{1}'.format( - street.get('street_name'), - street.get('zipcode'))) + if isinstance(response, dict) and 'errors' in response.keys(): + print('. '.join(response['errors'])) + else: + for street in response: + print('Street Name:\t{0}\t\tZip Code:\t{1}'.format( + street.get('street_name'), + street.get('zipcode'))) if __name__ == '__main__': diff --git a/examples/routes/duplicate_route.py b/examples/routes/duplicate_route.py index cec8ccf..2bffeb0 100644 --- a/examples/routes/duplicate_route.py +++ b/examples/routes/duplicate_route.py @@ -16,15 +16,20 @@ def main(api_key): route_id = response[0]['route_id'] print('Route ID: {}'.format(route_id)) response = route.duplicate_route(route_id=route_id) + + route_id = response.get('route_id') + opt = response.get('optimization_problem_id') + addresses = response.get('addresses', []) + if isinstance(response, dict) and 'errors' in response.keys(): print('. '.join(response['errors'])) - else: - print('Optimization Problem ID: {}'.format( - response['optimization_problem_id'] - )) - print('Route ID: {}'.format(response['route_id'])) - for address in response['addresses']: + elif opt and route_id and addresses: + print('Optimization Problem ID: {}'.format(opt)) + print('Route ID: {}'.format(route_id)) + for address in addresses: print('\t\t\tAddress: {0}'.format(address['address'])) + else: + print('Duplicate Route failed...') if __name__ == '__main__': diff --git a/examples/routes/resequence_multiple_stops.py b/examples/routes/resequence_multiple_stops.py index 8392d37..7003eab 100644 --- a/examples/routes/resequence_multiple_stops.py +++ b/examples/routes/resequence_multiple_stops.py @@ -23,10 +23,13 @@ def main(args): print(f'Address Sequence: {address["sequence_no"]:6} - ' f'Route Destination ID: {address["route_destination_id"]:9}') print(f"After Resequence the Route {args.route_id}") - response_data = route.resequence_multiple_stops(args.route_id, route_data) - for address in response_data['addresses']: - print(f'Address Sequence: {address["sequence_no"]:6} - ' - f'Route Destination ID: {address["route_destination_id"]:9} - Address: {address["address"]} ') + response = route.resequence_multiple_stops(args.route_id, route_data) + if isinstance(response, dict) and 'errors' in response.keys(): + print('. '.join(response['errors'])) + else: + for address in response['addresses']: + print(f'Address Sequence: {address["sequence_no"]:6} - ' + f'Route Destination ID: {address["route_destination_id"]:9} - Address: {address["address"]} ') if __name__ == '__main__': diff --git a/examples/routes/update_route.py b/examples/routes/update_route.py index d122538..1b0aa08 100644 --- a/examples/routes/update_route.py +++ b/examples/routes/update_route.py @@ -83,13 +83,19 @@ def main(api_key): ) response = r4m.run_optimization() - data = {"driver_alias": "Juan", } - route_id = response['addresses'][1]['route_id'] - print('Route ID: {}'.format(route_id)) - print('Driver Alias: {}'.format(data['driver_alias'])) - response = r4m.route.update_route(data, route_id) - print('Updating Route') - print('Driver Alias: {}'.format(response['driver_alias'])) + if isinstance(response, dict) and 'errors' in response.keys(): + print('. '.join(response['errors'])) + else: + data = {"driver_alias": "Juan", } + route_id = response['addresses'][1]['route_id'] + print('Route ID: {}'.format(route_id)) + print('Driver Alias: {}'.format(data['driver_alias'])) + response = r4m.route.update_route(data, route_id) + if isinstance(response, dict) and 'errors' in response.keys(): + print('. '.join(response['errors'])) + else: + print('Updating Route') + print('Driver Alias: {}'.format(response['driver_alias'])) # codebeat:enable[LOC, ABC] diff --git a/examples/routes/update_route_parameters.py b/examples/routes/update_route_parameters.py index ac556c7..6a30535 100644 --- a/examples/routes/update_route_parameters.py +++ b/examples/routes/update_route_parameters.py @@ -82,18 +82,23 @@ def main(api_key): response = r4m.run_optimization() - route_id = response['addresses'][1]['route_id'] + if isinstance(response, dict) and 'errors' in response.keys(): + print('. '.join(response['errors'])) + else: + route_id = response['addresses'][1]['route_id'] - parameters = { - "parameters": { - "member_id": 1, - "route_name": "Route name updated", + parameters = { + "parameters": { + "member_id": 1, + "route_name": "Route name updated", + } } - } - response = r4m.route.update_route_parameters(parameters, route_id) - - print('Route Name: {}'.format(response['parameters']['route_name'])) + response = r4m.route.update_route_parameters(parameters, route_id) + if isinstance(response, dict) and 'errors' in response.keys(): + print('. '.join(response['errors'])) + else: + print('Route Name: {}'.format(response['parameters']['route_name'])) if __name__ == '__main__': diff --git a/route4me/activity_feed.py b/route4me/activity_feed.py index b6a4e00..a726510 100644 --- a/route4me/activity_feed.py +++ b/route4me/activity_feed.py @@ -3,6 +3,7 @@ from .api_endpoints import ACTIVITY_FEED from .base import Base from .exceptions import ParamValueException +from .exceptions import APIException class ActivityFeed(Base): @@ -27,9 +28,13 @@ def get_activities_feed(self, **kwargs): """ kwargs.update({'api_key': self.params['api_key'], }) if self.check_required_params(kwargs, ['api_key', ]): - self.response = self.api._request_get(ACTIVITY_FEED, - kwargs) - return self.response.json() + try: + self.response = self.api._make_request(ACTIVITY_FEED, + kwargs, + self.api._request_get) + return self.response.json() + except APIException as e: + return e.to_dict() else: raise ParamValueException('params', 'Params are not complete') @@ -41,9 +46,13 @@ def get_activities_feed_by_type(self, **kwargs): """ kwargs.update({'api_key': self.params['api_key'], }) if self.check_required_params(kwargs, ['api_key', 'activity_type']): - self.response = self.api._request_get(ACTIVITY_FEED, - kwargs) - return self.response.json() + try: + self.response = self.api._make_request(ACTIVITY_FEED, + kwargs, + self.api._request_get) + return self.response.json() + except APIException as e: + return e.to_dict() else: raise ParamValueException('params', 'Params are not complete') @@ -56,9 +65,13 @@ def get_activity_feed_inserted(self, **kwargs): kwargs.update({'api_key': self.params['api_key'], 'activity_type': 'insert-destination', }) if self.check_required_params(kwargs, ['api_key', 'route_id']): - self.response = self.api._request_get(ACTIVITY_FEED, - kwargs) - return self.response.json() + try: + self.response = self.api._make_request(ACTIVITY_FEED, + kwargs, + self.api._request_get) + return self.response.json() + except APIException as e: + return e.to_dict() else: raise ParamValueException('params', 'Params are not complete') @@ -71,9 +84,13 @@ def get_activity_feed_deleted(self, **kwargs): kwargs.update({'api_key': self.params['api_key'], 'activity_type': 'delete-destination', }) if self.check_required_params(kwargs, ['api_key', 'route_id']): - self.response = self.api._request_get(ACTIVITY_FEED, - kwargs) - return self.response.json() + try: + self.response = self.api._make_request(ACTIVITY_FEED, + kwargs, + self.api._request_get) + return self.response.json() + except APIException as e: + return e.to_dict() else: raise ParamValueException('params', 'Params are not complete') @@ -86,9 +103,13 @@ def get_activity_feed_route_owner_changed(self, **kwargs): kwargs.update({'api_key': self.params['api_key'], 'activity_type': 'route-owner-changed', }) if self.check_required_params(kwargs, ['api_key', 'route_id']): - self.response = self.api._request_get(ACTIVITY_FEED, - kwargs) - return self.response.json() + try: + self.response = self.api._make_request(ACTIVITY_FEED, + kwargs, + self.api._request_get) + return self.response.json() + except APIException as e: + return e.to_dict() else: raise ParamValueException('params', 'Params are not complete') @@ -104,8 +125,13 @@ def log_specific_message(self, **kwargs): if self.check_required_params(self.json_data, ['api_key', 'activity_message', 'route_id']): - self.response = self.api._request_post(ACTIVITY_FEED, - self.params, json=self.json_data) - return self.response.json() + try: + self.response = self.api._make_request(ACTIVITY_FEED, + self.params, + self.api._request_post, + json=self.json_data) + return self.response.json() + except APIException as e: + return e.to_dict() else: raise ParamValueException('params', 'Params are not complete') diff --git a/route4me/address.py b/route4me/address.py index 6b4e691..434ca88 100644 --- a/route4me/address.py +++ b/route4me/address.py @@ -5,6 +5,7 @@ import random import time import requests +from .exceptions import APIException from .api_endpoints import ( ADD_ROUTE_NOTES_HOST, @@ -102,11 +103,14 @@ def get_geocode(self, params): :param params: :return: response as a object """ - self.response = self.api._make_request(SINGLE_GEOCODER, - params, - [], - self.api._request_get) - return self.response.json() + try: + self.response = self.api._make_request(SINGLE_GEOCODER, + params, + self.api._request_get, + data=[]) + return self.response.json() + except APIException as e: + return e.to_dict() def get_batch_geocodes(self, params, data): """ @@ -115,11 +119,14 @@ def get_batch_geocodes(self, params, data): :param data: :return: response as a object """ - self.response = self.api._make_request(BATCH_GEOCODER, - params, - data, - self.api._request_post) - return self.response.json() + try: + self.response = self.api._make_request(BATCH_GEOCODER, + params, + self.api._request_post, + data=data) + return self.response.json() + except APIException as e: + return e.to_dict() def fix_geocode(self, address): geocoding_error = None @@ -141,10 +148,13 @@ def fix_geocode(self, address): def request_address(self, params): params.update({'api_key': self.api.key}) - return self.api._make_request(ADDRESS_HOST, - params, - None, - self.api._request_get) + try: + response = self.api._make_request(ADDRESS_HOST, + params, + self.api._request_get) + return response + except APIException as e: + return e.to_dict() def get_address(self, route_id, route_destination_id): params = {'route_id': route_id, @@ -166,21 +176,27 @@ def update_address(self, data, route_id, route_destination_id): 'route_destination_id': route_destination_id } params.update({'api_key': self.api.key}) - response = self.api._request_put(ADDRESS_HOST, - params, - json=data) - return response.json() + try: + response = self.api._make_request(ADDRESS_HOST, + params, + self.api._request_put, + json=data) + return response.json() + except APIException as e: + return e.to_dict() def delete_address_from_route(self, route_id, route_destination_id): params = {'route_id': route_id, 'route_destination_id': route_destination_id } params.update({'api_key': self.api.key}) - response = self.api._make_request(ADDRESS_HOST, - params, - None, - self.api._request_delete) - return response.json() + try: + response = self.api._make_request(ADDRESS_HOST, + params, + self.api._request_delete) + return response.json() + except APIException as e: + return e.to_dict() def add_address_notes(self, note, **kwargs): """ @@ -192,9 +208,14 @@ def add_address_notes(self, note, **kwargs): data = {'strUpdateType': kwargs.pop('activity_type'), 'strNoteContents': note} kwargs.update({'api_key': self.params['api_key'], }) - self.response = self.api._request_post(ADD_ROUTE_NOTES_HOST, - kwargs, data) - return self.response.json() + try: + self.response = self.api._make_request(ADD_ROUTE_NOTES_HOST, + kwargs, + self.api._request_post, + data=data) + return self.response.json() + except APIException as e: + return e.to_dict() else: raise ParamValueException('params', 'Params are not complete') @@ -208,10 +229,13 @@ def geocode(self, **kwargs): kwargs.update({'format': 'csv'}) kwargs.update({'api_key': self.params['api_key'], }) if self.check_required_params(kwargs, ['addresses', ]): - response = self.api._request_post(BATCH_GEOCODER, - kwargs) - return response.content - + try: + response = self.api._make_request(BATCH_GEOCODER, + kwargs, + self.api._request_post) + return response.content + except APIException as e: + return e.to_dict() else: raise ParamValueException('params', 'Params are not complete') diff --git a/route4me/address_book.py b/route4me/address_book.py index 181448d..e78eb03 100644 --- a/route4me/address_book.py +++ b/route4me/address_book.py @@ -2,7 +2,7 @@ from .api_endpoints import ADDRESSBOOK from .base import Base -from .exceptions import ParamValueException +from .exceptions import ParamValueException, APIException class AddressBook(Base): @@ -28,10 +28,14 @@ def create_contact(self, **kwargs): """ self.json_data = kwargs if self.check_required_params(self.json_data, self.REQUIRED_FIELDS): - self.response = self.api._request_post(ADDRESSBOOK, - self.params, - json=self.json_data) - return self.response.json() + try: + self.response = self.api._make_request(ADDRESSBOOK, + self.params, + self.api._request_post, + json=self.json_data) + return self.response.json() + except APIException as e: + return e.to_dict() else: raise ParamValueException('params', 'Params are not complete') @@ -43,9 +47,13 @@ def get_addressbook_contacts(self, **kwargs): """ kwargs.update({'api_key': self.params['api_key'], }) if self.check_required_params(kwargs, ['api_key', ]): - self.response = self.api._request_get(ADDRESSBOOK, - kwargs) - return self.response.json() + try: + self.response = self.api._make_request(ADDRESSBOOK, + kwargs, + self.api._request_get) + return self.response.json() + except APIException as e: + return e.to_dict() else: raise ParamValueException('params', 'Params are not complete') @@ -57,9 +65,13 @@ def get_addressbook_contact(self, **kwargs): """ kwargs.update({'api_key': self.params['api_key'], }) if self.check_required_params(kwargs, ['address_id', ]): - self.response = self.api._request_get(ADDRESSBOOK, - kwargs) - return self.response.json() + try: + self.response = self.api._make_request(ADDRESSBOOK, + kwargs, + self.api._request_get) + return self.response.json() + except APIException as e: + return e.to_dict() else: raise ParamValueException('params', 'Params are not complete') @@ -70,10 +82,14 @@ def update_contact(self, **kwargs): :raise: ParamValueException if required params are not present. """ if self.check_required_params(kwargs, ['address_id', ]): - self.response = self.api._request_put(ADDRESSBOOK, - self.params, - json=kwargs) - return self.response.json() + try: + self.response = self.api._make_request(ADDRESSBOOK, + self.params, + self.api._request_put, + json=kwargs) + return self.response.json() + except APIException as e: + return e.to_dict() else: raise ParamValueException('params', 'Params are not complete') @@ -84,9 +100,13 @@ def delete_addressbook_contact(self, **kwargs): :raise: ParamValueException if required params are not present. """ if self.check_required_params(kwargs, ['address_ids', ]): - self.response = self.api._request_delete(ADDRESSBOOK, - self.params, - json=kwargs) - return self.response.json() + try: + self.response = self.api._make_request(ADDRESSBOOK, + self.params, + self.api._request_delete, + json=kwargs) + return self.response.json() + except APIException as e: + return e.to_dict() else: raise ParamValueException('params', 'Params are not complete') diff --git a/route4me/api.py b/route4me/api.py index bcf35aa..d189e97 100644 --- a/route4me/api.py +++ b/route4me/api.py @@ -63,18 +63,18 @@ def __init__(self, self.proxies = proxies self.route_status = RouteStatus(self) - def _make_request(self, url, params, data, request_method): + def _make_request(self, url, params, request_method, **kwargs): """ Make request to API :param url: :param params: - :param data: :param request_method: + :param kwargs: additional arguments :return: response :raise: APIException """ params['api_key'] = self.key - response = request_method(url, params, data) + response = request_method(url, params, **kwargs) if not 200 <= response.status_code < 400: raise APIException(response.status_code, response.text, response.url) @@ -92,8 +92,8 @@ def get(self, request_method): 'redirect': 0, }) return self._make_request(API_HOST, params, - json.dumps(self.optimization.data), - request_method) + request_method, + data=json.dumps(self.optimization.data)) def _request_post(self, url, request_params, data=None, json=None, files=None): """ diff --git a/route4me/avoidance_zones.py b/route4me/avoidance_zones.py index 471d0cc..9bcc345 100644 --- a/route4me/avoidance_zones.py +++ b/route4me/avoidance_zones.py @@ -4,7 +4,7 @@ from .api_endpoints import AVOIDANCE from .base import Base -from .exceptions import ParamValueException +from .exceptions import ParamValueException, APIException class AvoindanceZones(Base): @@ -28,9 +28,13 @@ def get_avoidance_zones(self): :raise: ParamValueException if required params are not present. """ if self.check_required_params(self.params, ['api_key', ]): - self.response = self.api._request_get(AVOIDANCE, - self.params) - return self.response.json() + try: + self.response = self.api._make_request(AVOIDANCE, + self.params, + self.api._request_get) + return self.response.json() + except APIException as e: + return e.to_dict() else: raise ParamValueException('params', 'Params are not complete') @@ -42,9 +46,13 @@ def get_avoidance_zone(self, **kwargs): """ kwargs.update({'api_key': self.params['api_key'], }) if self.check_required_params(kwargs, ['api_key', 'territory_id']): - self.response = self.api._request_get(AVOIDANCE, - kwargs) - return self.response.json() + try: + self.response = self.api._make_request(AVOIDANCE, + kwargs, + self.api._request_get) + return self.response.json() + except APIException as e: + return e.to_dict() else: raise ParamValueException('params', 'Params are not complete') @@ -57,10 +65,14 @@ def add_avoidance_zone(self, **kwargs): if self.check_required_params(kwargs, ['territory_name', 'territory_color', 'territory']): - self.response = self.api._request_post(AVOIDANCE, - self.params, - json=kwargs) - return self.response.json() + try: + self.response = self.api._make_request(AVOIDANCE, + self.params, + self.api._request_post, + json=kwargs) + return self.response.json() + except APIException as e: + return e.to_dict() else: raise ParamValueException('params', 'Params are not complete') @@ -72,9 +84,13 @@ def delete_avoidance_zone(self, **kwargs): """ kwargs.update({'api_key': self.params['api_key'], }) if self.check_required_params(kwargs, ['territory_id']): - self.response = self.api._request_delete(AVOIDANCE, - kwargs) - return self.response.json() + try: + self.response = self.api._make_request(AVOIDANCE, + kwargs, + self.api._request_delete) + return self.response.json() + except APIException as e: + return e.to_dict() else: raise ParamValueException('params', 'Params are not complete') @@ -88,10 +104,14 @@ def update_avoidance_zone(self, territory_id, **kwargs): if self.check_required_params(kwargs, ['territory_name', 'territory_color', 'territory']): - self.response = self.api._request_put(AVOIDANCE, - self.params, - json=kwargs) - return self.response.json() + try: + self.response = self.api._make_request(AVOIDANCE, + self.params, + self.api._request_put, + json=kwargs) + return self.response.json() + except APIException as e: + return e.to_dict() else: raise ParamValueException('params', 'Params are not complete') diff --git a/route4me/base.py b/route4me/base.py index 2f70070..c18a17e 100644 --- a/route4me/base.py +++ b/route4me/base.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # codebeat:disable[TOTAL_LOC, TOO_MANY_FUNCTIONS, TOTAL_COMPLEXITY] -import re +from route4me.utils import is_valid_datetime from .constants import ( TRAVEL_MODE, @@ -221,8 +221,7 @@ def device_timestamp(self, device_timestamp): :param device_timestamp: :return: """ - pattern = r'^(\d{4})-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9])$' - if re.match(pattern, device_timestamp): + if is_valid_datetime(device_timestamp): self._copy_param({'device_timestamp': device_timestamp}) else: raise ParamValueException('device_timestamp', diff --git a/route4me/exceptions.py b/route4me/exceptions.py index fdc2c06..02a947e 100644 --- a/route4me/exceptions.py +++ b/route4me/exceptions.py @@ -1,5 +1,7 @@ # -*- coding: utf-8 -*- +import json + DRIVER_VERSION = 'route4me-python-driver-0.0.1' @@ -32,6 +34,16 @@ def get_response(self): """ return self.response + def to_dict(self): + """ + Return a dictionary representation of the exception + """ + try: + response_dict = json.loads(self.response) + return {'errors': response_dict.get('errors')} + except json.JSONDecodeError: + return {'errors': 'Unexpected server response'} + class ParamValueException(Exception): """ diff --git a/route4me/file_uploading.py b/route4me/file_uploading.py index a0d8ccf..a347b6c 100644 --- a/route4me/file_uploading.py +++ b/route4me/file_uploading.py @@ -3,7 +3,7 @@ from .api_endpoints import FILE_UPLOAD_HOST, \ FILE_UPLOAD_PREVIEW_HOST, FILE_UPLOAD_GEOCODE_HOST from .base import Base -from .exceptions import ParamValueException +from .exceptions import ParamValueException, APIException class FileUploading(Base): @@ -28,9 +28,13 @@ def get_file_preview(self, **kwargs): """ kwargs.update({'api_key': self.params['api_key'], }) if self.check_required_params(kwargs, ['strUploadID', 'format']): - self.response = self.api._request_get(FILE_UPLOAD_PREVIEW_HOST, - kwargs) - return self.response.content + try: + self.response = self.api._make_request(FILE_UPLOAD_PREVIEW_HOST, + kwargs, + self.api._request_get) + return self.response.content + except APIException as e: + return e.to_dict() else: raise ParamValueException('params', 'Params are not complete') @@ -44,10 +48,14 @@ def upload_file(self, **kwargs): kwargs.update({'api_key': self.params['api_key'], }) if self.check_required_params(kwargs, ['files', 'format', ]): - self.response = self.api._request_post(FILE_UPLOAD_HOST, - kwargs, - files=kwargs.pop('files')) - return self.response.content + try: + self.response = self.api._make_request(FILE_UPLOAD_HOST, + kwargs, + self.api._request_post, + files=kwargs.pop('files')) + return self.response.content + except APIException as e: + return e.to_dict() else: raise ParamValueException('params', 'Params are not complete') @@ -60,9 +68,13 @@ def upload_file_geocode(self, **kwargs): """ kwargs.update({'api_key': self.params['api_key'], }) if self.check_required_params(kwargs, ['strUploadID', 'files', ]): - self.response = self.api._request_post(FILE_UPLOAD_GEOCODE_HOST, - kwargs, - files=kwargs.pop('files')) - return self.response.content + try: + self.response = self.api._make_request(FILE_UPLOAD_GEOCODE_HOST, + kwargs, + self.api._request_post, + files=kwargs.pop('files')) + return self.response.content + except APIException as e: + return e.to_dict() else: raise ParamValueException('params', 'Params are not complete') diff --git a/route4me/gps.py b/route4me/gps.py index a13f692..2277ad8 100644 --- a/route4me/gps.py +++ b/route4me/gps.py @@ -2,7 +2,7 @@ from .api_endpoints import SET_GPS_HOST, DEVICE_LOCATION_URL from .base import Base -from .exceptions import ParamValueException +from .exceptions import ParamValueException, APIException class GPS(Base): @@ -36,9 +36,13 @@ def set_gps_track(self, **kwargs): """ kwargs.update(self.params) if self.check_required_params(kwargs, self.requirements): - self.response = self.api._request_get(SET_GPS_HOST, - kwargs) - return self.response.json() + try: + self.response = self.api._make_request(SET_GPS_HOST, + kwargs, + self.api._request_get) + return self.response.json() + except APIException as e: + return e.to_dict() else: raise ParamValueException('params', 'Params are not complete') @@ -56,8 +60,12 @@ def get_locations(self, **kwargs): 'member_id', 'time_period', ]): - self.response = self.api._request_get(DEVICE_LOCATION_URL, - kwargs) - return self.response.json() + try: + self.response = self.api._make_request(DEVICE_LOCATION_URL, + kwargs, + self.api._request_get) + return self.response.json() + except APIException as e: + return e.to_dict() else: raise ParamValueException('params', 'Params are not complete') diff --git a/route4me/members.py b/route4me/members.py index bc9cef3..e58f260 100644 --- a/route4me/members.py +++ b/route4me/members.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- import json +from .exceptions import APIException from .api_endpoints import ( MEMBER_AUTHENTICATE, @@ -43,13 +44,17 @@ def app_purchase_user_license(self, **kwargs): 'token', 'payload', 'format', ]): - response = self.api._request_post(USER_LICENSE_HOST, - self.params, - json=kwargs) try: - return json.loads(response.content) - except ValueError: - return response.content + response = self.api._make_request(USER_LICENSE_HOST, + self.params, + self.api._request_post, + json=kwargs) + try: + return json.loads(response.content) + except ValueError: + return response.content + except APIException as e: + return e.to_dict() else: raise ParamValueException('order', 'Missing required params') @@ -62,13 +67,17 @@ def verify_device_license(self, **kwargs): if self.check_required_params(kwargs, ['device_id', 'device_type', 'format', ]): - response = self.api._request_post(VERIFY_DEVICE_LICENSE, - self.params, - json=kwargs) try: - return json.loads(response.content) - except ValueError: - return response.content + response = self.api._make_request(VERIFY_DEVICE_LICENSE, + self.params, + self.api._request_post, + json=kwargs) + try: + return json.loads(response.content) + except ValueError: + return response.content + except APIException as e: + return e.to_dict() else: raise ParamValueException('order', 'Missing required params') @@ -82,13 +91,17 @@ def member_authenticate(self, **kwargs): 'password', ]): kwargs['strEmail'] = kwargs.pop('email') kwargs['strPassword'] = kwargs.pop('password') - response = self.api._request_post(MEMBER_AUTHENTICATE, - self.params, - data=kwargs) try: - return json.loads(response.content) - except ValueError: - return response.content + response = self.api._make_request(MEMBER_AUTHENTICATE, + self.params, + self.api._request_post, + json=kwargs) + try: + return json.loads(response.content) + except ValueError: + return response.content + except APIException as e: + return e.to_dict() else: raise ParamValueException('order', 'Missing required params') @@ -98,12 +111,16 @@ def get_users(self, **kwargs): :return: API response """ kwargs.update({'api_key': self.params['api_key'], }) - response = self.api._request_get(GET_USERS_HOST, - kwargs) try: - return response.json() - except ValueError: - return response.content + response = self.api._make_request(GET_USERS_HOST, + kwargs, + self.api._request_get) + try: + return response.json() + except ValueError: + return response.content + except APIException as e: + return e.to_dict() def get_api_key_users(self, **kwargs): """ @@ -111,12 +128,16 @@ def get_api_key_users(self, **kwargs): :return: API response """ kwargs.update({'api_key': self.params['api_key'], }) - response = self.api._request_get(USER_URL, - kwargs) try: - return response.json() - except ValueError: - return response.content + response = self.api._make_request(USER_URL, + kwargs, + self.api._request_get) + try: + return response.json() + except ValueError: + return response.content + except APIException as e: + return e.to_dict() def validate_session(self, **kwargs): """ @@ -127,12 +148,16 @@ def validate_session(self, **kwargs): kwargs.update({'api_key': self.params['api_key'], }) if self.check_required_params(kwargs, ['session_guid', 'member_id', ]): - response = self.api._request_get(VALIDATE_SESSION, - kwargs) try: - return json.loads(response.content) - except ValueError: - return response.content + response = self.api._make_request(VALIDATE_SESSION, + kwargs, + self.api._request_get) + try: + return json.loads(response.content) + except ValueError: + return response.content + except APIException as e: + return e.to_dict() else: raise ParamValueException('order', 'Missing required params') @@ -149,13 +174,17 @@ def webinar_registration(self, **kwargs): "company_name", "member_id", "webiinar_date"]): - response = self.api._request_post(WEBINAR_REGISTER, - self.params, - data=kwargs) try: - return json.loads(response.content) - except ValueError: - return response.content + response = self.api._make_request(WEBINAR_REGISTER, + self.params, + self.api._request_post, + data=kwargs) + try: + return json.loads(response.content) + except ValueError: + return response.content + except APIException as e: + return e.to_dict() else: raise ParamValueException('order', 'Missing required params') @@ -184,12 +213,16 @@ def register(self, **kwargs): kwargs['device_type'] = kwargs.pop('device_type') kwargs['strPassword_1'] = kwargs.pop('password_1') kwargs['strPassword_2'] = kwargs.pop('password_2') - response = self.api._request_post(REGISTER_ACTION, - params, - data=kwargs) try: - return response.json() - except ValueError: - return response.content + response = self.api._make_request(REGISTER_ACTION, + params, + self.api._request_post, + data=kwargs) + try: + return response.json() + except ValueError: + return response.content + except APIException as e: + return e.to_dict() else: raise ParamValueException('order', 'Missing required params') diff --git a/route4me/optimization.py b/route4me/optimization.py index 318093b..f4db1ac 100644 --- a/route4me/optimization.py +++ b/route4me/optimization.py @@ -2,7 +2,7 @@ from .api_endpoints import ADDRESS_HOST, API_HOST from .base import Base -from .exceptions import ParamValueException +from .exceptions import ParamValueException, APIException class Optimization(Base): @@ -31,10 +31,13 @@ def get_optimizations(self, **kwargs): """ kwargs.update({'api_key': self.params['api_key'], }) if self.check_required_params(kwargs, ['limit', 'offset', ]): - self.response = self.api._request_get(API_HOST, - kwargs) - response = self.response.json() - return response + try: + response = self.api._make_request(API_HOST, + kwargs, + self.api._request_get) + return response.json() + except APIException as e: + return e.to_dict() else: raise ParamValueException('params', 'Params are not complete') @@ -47,10 +50,13 @@ def get_optimization(self, **kwargs): """ kwargs.update({'api_key': self.params['api_key'], }) if self.check_required_params(kwargs, ['optimization_problem_id', ]): - self.response = self.api._request_get(API_HOST, - kwargs) - response = self.response.json() - return response + try: + response = self.api._make_request(API_HOST, + kwargs, + self.api._request_get) + return response.json() + except APIException as e: + return e.to_dict() else: raise ParamValueException('params', 'Params are not complete') @@ -65,10 +71,14 @@ def update_optimization(self, **kwargs): if self.check_required_params(kwargs, ['optimization_problem_id', 'addresses', 'reoptimize']): - self.response = self.api._request_put(API_HOST, - kwargs) - response = self.response.json() - return response + try: + response = self.api._make_request(API_HOST, + self.params, + self.api._request_put, + data=kwargs) + return response.json() + except APIException as e: + return e.to_dict() else: raise ParamValueException('params', 'Params are not complete') @@ -81,11 +91,14 @@ def delete_optimization(self, **kwargs): """ self.json_data = kwargs if self.check_required_params(kwargs, ['optimization_problem_ids', ]): - self.response = self.api._request_delete(API_HOST, - self.params, - json=kwargs) - response = self.response.json() - return response + try: + response = self.api._make_request(API_HOST, + self.params, + self.api._request_delete, + json=kwargs) + return response.json() + except APIException as e: + return e.to_dict() else: raise ParamValueException('params', 'Params are not complete') @@ -99,9 +112,12 @@ def delete_address_from_optimization(self, **kwargs): kwargs.update({'api_key': self.params['api_key'], }) if self.check_required_params(kwargs, ['optimization_problem_id', 'route_destination_id']): - self.response = self.api._request_delete(ADDRESS_HOST, - kwargs) - response = self.response.json() - return response + try: + response = self.api._make_request(ADDRESS_HOST, + kwargs, + self.api._request_delete) + return response.json() + except APIException as e: + return e.to_dict() else: raise ParamValueException('params', 'Params are not complete') diff --git a/route4me/orders.py b/route4me/orders.py index c6b700d..3552c90 100644 --- a/route4me/orders.py +++ b/route4me/orders.py @@ -4,7 +4,7 @@ from .api_endpoints import ORDERS_HOST from .base import Base -from .exceptions import ParamValueException +from .exceptions import ParamValueException, APIException class Order(Base): @@ -29,13 +29,17 @@ def create_order(self, **kwargs): :return: API response content """ if self.check_required_params(kwargs, self.REQUIRED_FIELDS): - response = self.api._request_post(ORDERS_HOST, - self.params, - json=kwargs) try: - return response.json() - except ValueError: - return response.content + response = self.api._make_request(ORDERS_HOST, + self.params, + self.api._request_post, + json=kwargs) + try: + return response.json() + except ValueError: + return response.content + except APIException as e: + return e.to_dict() else: raise ParamValueException('order', 'Missing required params') @@ -47,12 +51,16 @@ def get_order(self, **kwargs): """ kwargs.update({'api_key': self.params['api_key'], }) if self.check_required_params(kwargs, ['order_id', 'api_key', ]): - response = self.api._request_get(ORDERS_HOST, - kwargs) try: - return json.loads(response.content) - except ValueError: - return response.content + response = self.api._make_request(ORDERS_HOST, + kwargs, + self.api._request_get) + try: + return json.loads(response.content) + except ValueError: + return response.content + except APIException as e: + return e.to_dict() else: raise ParamValueException('order', 'Missing required params') @@ -63,13 +71,17 @@ def update_order(self, **kwargs): :return: API response content """ if self.check_required_params(kwargs, self.REQUIRED_FIELDS): - response = self.api._request_put(ORDERS_HOST, - self.params, - json=kwargs) try: - return json.loads(response.content) - except ValueError: - return response.content + response = self.api._make_request(ORDERS_HOST, + self.params, + self.api._request_put, + json=kwargs) + try: + return json.loads(response.content) + except ValueError: + return response.content + except APIException as e: + return e.to_dict() else: raise ParamValueException('order', 'Missing required params') @@ -80,12 +92,16 @@ def delete_order(self, **kwargs): :return: API response content """ if self.check_required_params(kwargs, ['order_ids', ]): - response = self.api._request_delete(ORDERS_HOST, - self.params, - jsom=kwargs) try: - return json.loads(response.content) - except ValueError: - return response.content + response = self.api._make_request(ORDERS_HOST, + self.params, + self.api._request_delete, + json=kwargs) + try: + return json.loads(response.content) + except ValueError: + return response.content + except APIException as e: + return e.to_dict() else: raise ParamValueException('order', 'Missing required params') diff --git a/route4me/rapid_address.py b/route4me/rapid_address.py index 023d3b8..f276873 100644 --- a/route4me/rapid_address.py +++ b/route4me/rapid_address.py @@ -3,7 +3,7 @@ from .api_endpoints import RAPID_ADDRESS_SERVICE, \ RAPID_ADDRESS, RAPID_ADDRESS_ZIP from .base import Base -from .exceptions import ParamValueException +from .exceptions import ParamValueException, APIException class RapidAddress(Base): @@ -34,12 +34,15 @@ def get_street_data(self, **kwargs): kwargs.pop('limit')) elif 'pk' in kwargs: url = '{0}{1}/'.format(url, kwargs.pop('pk')) - - response = self.api._request_get(url, kwargs) - try: - return response.json() - except ValueError: - return response.content + try: + response = self.api._make_request(url, + kwargs, + self.api._request_get) + return response.json() + except APIException as e: + return e.to_dict() + except ValueError: + return response.content def get_street_data_zip(self, **kwargs): """ @@ -55,9 +58,13 @@ def get_street_data_zip(self, **kwargs): url = '{0}{1}/{2}/'.format(url, kwargs.pop('offset'), kwargs.pop('limit')) - response = self.api._request_get(url, kwargs) try: + response = self.api._make_request(url, + kwargs, + self.api._request_get) return response.json() + except APIException as e: + return e.to_dict() except ValueError: return response.content else: @@ -81,9 +88,13 @@ def get_street_data_service(self, **kwargs): url = '{0}{1}/{2}/'.format(url, kwargs.pop('offset'), kwargs.pop('limit')) - response = self.api._request_get(url, kwargs) try: + response = self.api._make_request(url, + kwargs, + self.api._request_get) return response.json() + except APIException as e: + return e.to_dict() except ValueError: return response.content else: diff --git a/route4me/route.py b/route4me/route.py index 13b8ef0..4b48e86 100644 --- a/route4me/route.py +++ b/route4me/route.py @@ -8,7 +8,7 @@ from .api_endpoints import MERGE_ROUTES_HOST, RESEQUENCE_ROUTE from .api_endpoints import EXPORTER_V5 from .base import Base -from .exceptions import ParamValueException +from .exceptions import ParamValueException, APIException class Route(Base): @@ -40,10 +40,13 @@ def get_route(self, **kwargs): if self.validate_params(**kwargs): self.params.update(kwargs) if self.check_required_params(self.params, self.requirements): - self.response = self.api._request_get(ROUTE_HOST, - self.params) - return self.response.json() - + try: + self.response = self.api._make_request(ROUTE_HOST, + self.params, + self.api._request_get) + return self.response.json() + except APIException as e: + return e.to_dict() else: raise ParamValueException('params', 'Params are not complete') @@ -57,9 +60,13 @@ def get_route_tracking(self, **kwargs): self.params.update(kwargs) self.params.update({'device_tracking_history': 1}) if self.check_required_params(self.params, self.requirements): - self.response = self.api._request_get(ROUTE_HOST, - self.params) - return self.response.json() + try: + self.response = self.api._make_request(ROUTE_HOST, + self.params, + self.api._request_get) + return self.response.json() + except APIException as e: + return e.to_dict() else: raise ParamValueException('params', 'Params are not complete') @@ -68,13 +75,16 @@ def get_routes(self, **kwargs): Get routes using GET request :return: API response :raise: ParamValueException if required params are not present. - """ kwargs.update({'api_key': self.params['api_key'], }) if self.check_required_params(kwargs, ['limit', 'offset', ]): - self.response = self.api._request_get(ROUTE_HOST, - kwargs) - return self.response.json() + try: + self.response = self.api._make_request(ROUTE_HOST, + kwargs, + self.api._request_get) + return self.response.json() + except APIException as e: + return e.to_dict() else: raise ParamValueException('params', 'Params are not complete') @@ -83,15 +93,18 @@ def get_activities(self, **kwargs): Get routes activities using GET request :return: API response :raise: ParamValueException if required params are not present. - """ kwargs.update({'api_key': self.params['api_key'], }) if self.check_required_params(kwargs, ['route_id', 'limit', 'offset', ]): - self.response = self.api._request_get(GET_ACTIVITIES_HOST, - kwargs) - return self.response.json() + try: + self.response = self.api._make_request(GET_ACTIVITIES_HOST, + kwargs, + self.api._request_get) + return self.response.json() + except APIException as e: + return e.to_dict() else: raise ParamValueException('params', 'Params are not complete') @@ -103,10 +116,13 @@ def duplicate_route(self, **kwargs): """ kwargs.update({'api_key': self.params['api_key'], }) if self.check_required_params(kwargs, ['route_id', ]): - self.response = self.api._request_get(DUPLICATE_ROUTE, - kwargs) - print(self.response.content) - return self.response.json() + try: + self.response = self.api._make_request(DUPLICATE_ROUTE, + kwargs, + self.api._request_get) + return self.response.json() + except APIException as e: + return e.to_dict() else: raise ParamValueException('params', 'Params are not complete') @@ -119,9 +135,13 @@ def delete_routes(self, **kwargs): kwargs.update({'api_key': self.params['api_key'], }) if self.check_required_params(kwargs, ['route_id', ]): kwargs['route_id'] = ','.join(kwargs['route_id']) - self.response = self.api._request_delete(ROUTE_HOST, - kwargs) - return self.response.json() + try: + self.response = self.api._make_request(ROUTE_HOST, + kwargs, + self.api._request_delete) + return self.response.json() + except APIException as e: + return e.to_dict() else: raise ParamValueException('params', 'Params are not complete') @@ -134,35 +154,36 @@ def delete_route(self, **kwargs): """ kwargs.update({'api_key': self.params['api_key'], }) if self.check_required_params(kwargs, ['route_id', ]): - self.response = self.api._request_delete(ROUTE_HOST, kwargs) - response = self.response.json() try: - response = response.get('deleted') - return response - except AttributeError: - return response.errors + self.response = self.api._make_request(ROUTE_HOST, + kwargs, + self.api._request_delete) + response = self.response.json() + try: + response = response.get('deleted') + return response + except AttributeError: + return response.errors + except APIException as e: + return e.to_dict() else: raise ParamValueException('params', 'Params are not complete') def insert_address_into_route(self, addresses, route_id): params = {'route_id': route_id} - response = self._update_route(params, addresses) - return response.json() + return self._update_route(params, addresses) def move_addresses_from_route(self, addresses, route_id): params = {'route_id': route_id} - response = self._update_route(params, addresses) - return response.json() + return self._update_route(params, addresses) def update_route_parameters(self, data, route_id): params = {'route_id': route_id} - response = self._update_route(params, data) - return response.json() + return self._update_route(params, data) def update_route(self, data, route_id): params = {'route_id': route_id} - response = self._update_route(params, data) - return response.json() + return self._update_route(params, data) def insert_address_into_route_optimal_position(self, **kwargs): """ @@ -176,10 +197,14 @@ def insert_address_into_route_optimal_position(self, **kwargs): 'optimal_position']): params = {'api_key': self.params['api_key'], 'route_id': kwargs.pop('route_id')} - response = self.api._request_put(ROUTE_HOST, - params, - json=kwargs) - return response.json() + try: + self.response = self.api._make_request(ROUTE_HOST, + params, + self.api._request_put, + json=kwargs) + return self.response.json() + except APIException as e: + return e.to_dict() else: raise ParamValueException('params', 'Params are not complete') @@ -193,10 +218,13 @@ def get_route_path_points(self, **kwargs): kwargs.update({'api_key': self.params['api_key'], }) if self.check_required_params(kwargs, ['route_path_output', 'route_id', ]): - response = self.api._request_get(ROUTE_HOST, - kwargs) - return response.json() - + try: + self.response = self.api._make_request(ROUTE_HOST, + kwargs, + self.api._request_get) + return self.response.json() + except APIException as e: + return e.to_dict() else: raise ParamValueException('params', 'Params are not complete') @@ -209,10 +237,13 @@ def get_route_directions(self, **kwargs): """ kwargs.update({'api_key': self.params['api_key'], }) if self.check_required_params(kwargs, ['directions', 'route_id', ]): - response = self.api._request_get(ROUTE_HOST, - kwargs) - return response.json() - + try: + self.response = self.api._make_request(ROUTE_HOST, + kwargs, + self.api._request_get) + return self.response.json() + except APIException as e: + return e.to_dict() else: raise ParamValueException('params', 'Params are not complete') @@ -229,11 +260,14 @@ def share_route(self, **kwargs): params = {'api_key': self.params['api_key'], 'route_id': kwargs.pop('route_id'), 'response_format': kwargs.pop('response_format')} - response = self.api._request_post(SHARE_ROUTE_HOST, - params, - json=kwargs) - return response.json() - + try: + self.response = self.api._make_request(SHARE_ROUTE_HOST, + params, + self.api._request_post, + json=kwargs) + return self.response.json() + except APIException as e: + return e.to_dict() else: raise ParamValueException('params', 'Params are not complete') @@ -252,10 +286,14 @@ def resequence_route(self, **kwargs): 'route_id': kwargs.pop('route_id'), 'route_destination_id': kwargs.pop('route_destination_id'), } - response = self.api._request_put(ROUTE_HOST, - params, json=kwargs) - return response.json() - + try: + self.response = self.api._make_request(ROUTE_HOST, + params, + self.api._request_put, + json=kwargs,) + return self.response.json() + except APIException as e: + return e.to_dict() else: raise ParamValueException('params', 'Params are not complete') @@ -271,10 +309,14 @@ def resequence_multiple_stops(self, route_id, addresses_data): 'api_key': self.params['api_key'], 'route_id': route_id, } - response = self.api._request_put(ROUTE_HOST, - params, json=addresses_data) - return response.json() - + try: + self.response = self.api._make_request(ROUTE_HOST, + params, + self.api._request_put, + json=addresses_data) + return self.response.json() + except APIException as e: + return e.to_dict() else: raise ParamValueException('params', 'Params are not complete') @@ -286,11 +328,14 @@ def merge_routes(self, **kwargs): AttributeError if there is an error deleting a route """ if self.check_required_params(kwargs, ['route_ids']): - response = self.api._request_post(MERGE_ROUTES_HOST, - self.params, - data=kwargs) - return response.json() - + try: + self.response = self.api._make_request(MERGE_ROUTES_HOST, + self.params, + self.api._request_post, + data=kwargs) + return self.response.json() + except APIException as e: + return e.to_dict() else: raise ParamValueException('params', 'Params are not complete') @@ -305,10 +350,13 @@ def resequence_route_all(self, **kwargs): if self.check_required_params(kwargs, ['disable_optimization', 'route_id', 'optimize', ]): - response = self.api._request_get(RESEQUENCE_ROUTE, - kwargs) - return response.json() - + try: + self.response = self.api._make_request(RESEQUENCE_ROUTE, + kwargs, + self.api._request_get) + return self.response.json() + except APIException as e: + return e.to_dict() else: raise ParamValueException('params', 'Params are not complete') @@ -326,10 +374,14 @@ def update_route_destination_custom_data(self, **kwargs): 'route_id': kwargs.pop('route_id'), 'route_destination_id': kwargs.pop('route_destination_id'), } - response = self.api._request_put(ADDRESS_HOST, - params, json=kwargs) - return response.json() - + try: + self.response = self.api._make_request(ADDRESS_HOST, + params, + self.api._request_put, + json=kwargs) + return self.response.json() + except APIException as e: + return e.to_dict() else: raise ParamValueException('params', 'Params are not complete') @@ -342,13 +394,22 @@ def export_route(self, route_id, output_format='csv'): """ print("DEPRECATED. Please use export_route_v5") data = {'route_id': route_id, 'strExportFormat': output_format} - self.response = self.api._make_request(EXPORTER, {}, data, - self.api._request_post) + self.response = self.api._make_request(EXPORTER, + {}, + self.api._request_post, + data=data) return self.response.content def _update_route(self, params, data): params.update({'api_key': self.api.key}) - return self.api._request_put(ROUTE_HOST, request_params=params, json=data) + try: + response = self.api._make_request(ROUTE_HOST, + params, + self.api._request_put, + data=data) + return response.json() + except APIException as e: + return e.to_dict() def export_route_v5(self, route_id, output_format='csv', all_custom_fields=True, columns=None): """ @@ -363,8 +424,14 @@ def export_route_v5(self, route_id, output_format='csv', all_custom_fields=True, data = {'all_custom_fields': all_custom_fields, 'format': output_format} if columns is not None: data["columns"] = columns - self.response = self.api._make_request(EXPORTER_V5.format(route_id=route_id), params, data, - self.api._request_post) - return self.response.content + try: + self.response = self.api._make_request(EXPORTER_V5.format(route_id=route_id), + params, + self.api._request_post, + data=data) + return self.response.content + except APIException as e: + return e.to_dict() + # codebeat:enable[TOO_MANY_FUNCTIONS] diff --git a/route4me/route_status.py b/route4me/route_status.py index 4421228..f3e83bd 100644 --- a/route4me/route_status.py +++ b/route4me/route_status.py @@ -3,6 +3,7 @@ from datetime import datetime from .base import Base from .api_endpoints import ROUTE_STATUS_V5 +from .exceptions import APIException class RouteStatus(Base): @@ -27,14 +28,22 @@ def __init__(self, api): Base.__init__(self, api) def get_route_status(self, route_id): - response = self.api._request_get("{}/{}".format(ROUTE_STATUS_V5, route_id), - self.params) - return response.json() + try: + response = self.api._make_request("{}/{}".format(ROUTE_STATUS_V5, route_id), + self.params, + self.api._request_get) + return response.json() + except APIException as e: + return e.to_dict() def get_route_status_history(self, route_id): - response = self.api._request_get("{}/{}/history".format(ROUTE_STATUS_V5, route_id), - self.params) - return response.json() + try: + response = self.api._make_request("{}/{}/history".format(ROUTE_STATUS_V5, route_id), + self.params, + self.api._request_get) + return response.json() + except APIException as e: + return e.to_dict() def set_route_status(self, route_id, status, lat, lng, event_timestamp=None): if event_timestamp is None: @@ -45,12 +54,20 @@ def set_route_status(self, route_id, status, lat, lng, event_timestamp=None): 'lng': lng, 'event_timestamp': event_timestamp, } - response = self.api._request_post("{}/{}".format(ROUTE_STATUS_V5, route_id), - self.params, - json=data) - return response.json() + try: + response = self.api._make_request("{}/{}".format(ROUTE_STATUS_V5, route_id), + self.params, + self.api._request_post, + json=data) + return response.json() + except APIException as e: + return e.to_dict() def rollback_route_status(self, route_id): - response = self.api._request_get("{}/{}/rollback".format(ROUTE_STATUS_V5, route_id), - self.params) - return response.json() + try: + response = self.api._make_request("{}/{}/rollback".format(ROUTE_STATUS_V5, route_id), + self.params, + self.api._request_get) + return response.json() + except APIException as e: + return e.to_dict() diff --git a/route4me/telematics.py b/route4me/telematics.py index 3ca5fd8..75b60bc 100644 --- a/route4me/telematics.py +++ b/route4me/telematics.py @@ -3,7 +3,7 @@ from .api_endpoints import TELEMATICS_VENDORS_V4, TELEMATICS_REGISTER_V4 from .api_endpoints import TELEMATICS_CONNECTIONS_V4, TELEMATICS_VENDORS_INFO_V4 from .base import Base -from .exceptions import ParamValueException +from .exceptions import ParamValueException, APIException class Telematics(Base): @@ -34,9 +34,13 @@ def pp_vendor_comparison(vendor): def get_vendors(self): if self.check_required_params(self.params, ['api_key', ]): - self.response = self.api._request_get(TELEMATICS_VENDORS_V4, - self.params) - return self.response.json() + try: + self.response = self.api._make_request(TELEMATICS_VENDORS_V4, + self.params, + self.api._request_get) + return self.response.json() + except APIException as e: + return e.to_dict() else: raise ParamValueException('params', 'Missing API KEY') @@ -45,9 +49,13 @@ def get_vendor(self, vendor_id): self.params.update({'vendor_id': vendor_id}) if self.check_required_params(self.params, ['api_key']): - self.response = self.api._request_get(TELEMATICS_VENDORS_V4, - self.params) - return self.response.json() + try: + self.response = self.api._make_request(TELEMATICS_VENDORS_V4, + self.params, + self.api._request_get) + return self.response.json() + except APIException as e: + return e.to_dict() else: raise ParamValueException('params', 'Missing API KEY') @@ -56,9 +64,13 @@ def search_vendor(self, **kwargs): kwargs.update({'api_key': self.params['api_key'], }) if self.check_required_params(kwargs, ['api_key']): - self.response = self.api._request_get(TELEMATICS_VENDORS_V4, - kwargs) - return self.response.json() + try: + self.response = self.api._make_request(TELEMATICS_VENDORS_V4, + kwargs, + self.api._request_get) + return self.response.json() + except APIException as e: + return e.to_dict() else: raise ParamValueException('params', 'Missing API KEY') @@ -67,9 +79,13 @@ def compare_vendors(self, vendors_id): self.params.update({'vendors': vendors_id}) if self.check_required_params(self.params, ['api_key']): - self.response = self.api._request_get(TELEMATICS_VENDORS_V4, - self.params) - return self.response.json() + try: + self.response = self.api._make_request(TELEMATICS_VENDORS_V4, + self.params, + self.api._request_get) + return self.response.json() + except APIException as e: + return e.to_dict() else: raise ParamValueException('params', 'Missing API KEY') @@ -77,9 +93,13 @@ def register_member(self, member_id): self.params.update({'member_id': member_id}) if self.check_required_params(self.params, ['api_key']): - self.response = self.api._request_get(TELEMATICS_REGISTER_V4, - self.params) - return self.response.json() + try: + self.response = self.api._make_request(TELEMATICS_REGISTER_V4, + self.params, + self.api._request_get) + return self.response.json() + except APIException as e: + return e.to_dict() else: raise ParamValueException('params', 'Missing API KEY') @@ -87,9 +107,13 @@ def get_connections(self, api_token, **kwargs): kwargs.update({"api_token": api_token}) if self.check_required_params(kwargs, ['api_token']): - self.response = self.api._request_get(TELEMATICS_CONNECTIONS_V4, - kwargs) - return self.response.json() + try: + self.response = self.api._make_request(TELEMATICS_CONNECTIONS_V4, + kwargs, + self.api._request_get) + return self.response.json() + except APIException as e: + return e.to_dict() else: raise ParamValueException('params', 'Missing API Token') @@ -97,9 +121,13 @@ def get_vendors_info(self, api_token, **kwargs): kwargs.update({"api_token": api_token}) if self.check_required_params(kwargs, ['api_token']): - self.response = self.api._request_get(TELEMATICS_VENDORS_INFO_V4, - kwargs) - return self.response.json() + try: + self.response = self.api._make_request(TELEMATICS_VENDORS_INFO_V4, + kwargs, + self.api._request_get) + return self.response.json() + except APIException as e: + return e.to_dict() else: raise ParamValueException('params', 'Missing API Token') @@ -107,10 +135,14 @@ def register_connection(self, api_token, **kwargs): params = {"api_token": api_token} kwargs.update({"validate_remote_credentials": "true"}) if self.check_required_params(kwargs, ['vendor_id']): - self.response = self.api._request_post(TELEMATICS_CONNECTIONS_V4, - params, - data=kwargs) - return self.response.json() + try: + self.response = self.api._make_request(TELEMATICS_CONNECTIONS_V4, + params, + self.api._request_post, + json=kwargs) + return self.response.json() + except APIException as e: + return e.to_dict() else: raise ParamValueException('params', 'Missing Vendor ID') @@ -119,18 +151,26 @@ def get_connection(self, api_token, connection_token): "api_token": api_token, "connection_token": connection_token } - self.response = self.api._request_get(TELEMATICS_CONNECTIONS_V4, - params) - return self.response.json() + try: + self.response = self.api._make_request(TELEMATICS_CONNECTIONS_V4, + params, + self.api._request_get) + return self.response.json() + except APIException as e: + return e.to_dict() def delete_connection(self, api_token, connection_token): params = { "api_token": api_token, "connection_token": connection_token } - self.response = self.api._request_delete(TELEMATICS_CONNECTIONS_V4, - params) - return self.response.json() + try: + self.response = self.api._make_request(TELEMATICS_CONNECTIONS_V4, + params, + self.api._request_delete) + return self.response.json() + except APIException as e: + e.to_dict() def update_connection(self, api_token, connection_token, **kwargs): params = { @@ -139,9 +179,13 @@ def update_connection(self, api_token, connection_token, **kwargs): } kwargs.update({"validate_remote_credentials": "true"}) if self.check_required_params(kwargs, ['vendor_id']): - self.response = self.api._request_put(TELEMATICS_CONNECTIONS_V4, - params, - data=kwargs) - return self.response.json() + try: + self.response = self.api._make_request(TELEMATICS_CONNECTIONS_V4, + params, + self.api._request_put, + json=kwargs) + return self.response.json() + except APIException as e: + return e.to_dict() else: raise ParamValueException('params', 'Missing Vendor ID') diff --git a/route4me/territory.py b/route4me/territory.py index 194b32a..ba1be11 100644 --- a/route4me/territory.py +++ b/route4me/territory.py @@ -3,7 +3,7 @@ from .api_endpoints import TERRITORY_HOST from .base import Base -from .exceptions import ParamValueException +from .exceptions import ParamValueException, APIException class Territory(Base): @@ -27,9 +27,13 @@ def get_territories(self): :raise: ParamValueException if required params are not present. """ if self.check_required_params(self.params, ['api_key', ]): - self.response = self.api._request_get(TERRITORY_HOST, - self.params) - return self.response.json() + try: + self.response = self.api._make_request(TERRITORY_HOST, + self.params, + self.api._request_get) + return self.response.json() + except APIException as e: + return e.to_dict() else: raise ParamValueException('params', 'Params are not complete') @@ -41,9 +45,13 @@ def get_territory(self, **kwargs): """ kwargs.update({'api_key': self.params['api_key'], }) if self.check_required_params(kwargs, ['api_key', 'territory_id']): - self.response = self.api._request_get(TERRITORY_HOST, - kwargs) - return self.response.json() + try: + self.response = self.api._make_request(TERRITORY_HOST, + kwargs, + self.api._request_get) + return self.response.json() + except APIException as e: + return e.to_dict() else: raise ParamValueException('params', 'Params are not complete') @@ -56,10 +64,14 @@ def add_territory(self, **kwargs): if self.check_required_params(kwargs, ['territory_name', 'territory_color', 'territory']): - self.response = self.api._request_post(TERRITORY_HOST, - self.params, - json=kwargs) - return self.response.json() + try: + self.response = self.api._make_request(TERRITORY_HOST, + self.params, + self.api._request_post, + json=kwargs) + return self.response.json() + except APIException as e: + return e.to_dict() else: raise ParamValueException('params', 'Params are not complete') @@ -71,9 +83,13 @@ def delete_territory(self, **kwargs): """ kwargs.update({'api_key': self.params['api_key'], }) if self.check_required_params(kwargs, ['territory_id']): - self.response = self.api._request_delete(TERRITORY_HOST, - json=kwargs) - return self.response.json() + try: + self.response = self.api._make_request(TERRITORY_HOST, + kwargs, + self.api._request_delete) + return self.response.json() + except APIException as e: + return e.to_dict() else: raise ParamValueException('params', 'Params are not complete') @@ -87,10 +103,14 @@ def update_territory(self, territory_id, **kwargs): if self.check_required_params(kwargs, ['territory_name', 'territory_color', 'territory']): - self.response = self.api._request_put(TERRITORY_HOST, - self.params, - json=kwargs) - return self.response.json() + try: + self.response = self.api._make_request(TERRITORY_HOST, + self.params, + self.api._request_put, + json=kwargs) + return self.response.json() + except APIException as e: + return e.to_dict() else: raise ParamValueException('params', 'Params are not complete') diff --git a/route4me/utils.py b/route4me/utils.py index bec815c..c2d6d37 100644 --- a/route4me/utils.py +++ b/route4me/utils.py @@ -2,6 +2,7 @@ import json from collections import namedtuple +from datetime import datetime def _json_object_hook(d): @@ -53,3 +54,16 @@ def clean_dict(data): else: # For non-container type, return as is return data + + +def is_valid_datetime(s): + """ + Checks if the given datetime string is in 'YYYY-MM-DD HH:MM:SS' format. + :param s: The datetime string to validate. + :return: True if the datetime string is valid, False otherwise. + """ + try: + datetime.strptime(str(s), '%Y-%m-%d %H:%M:%S') + return True + except ValueError: + return False diff --git a/route4me/vehicles.py b/route4me/vehicles.py index 1cce2c0..5bdd964 100644 --- a/route4me/vehicles.py +++ b/route4me/vehicles.py @@ -27,15 +27,13 @@ def get_vehicles(self): :raise: ParamValueException if required params are not present. """ if self.check_required_params(self.params, ['api_key', ]): - self.response = self.api._request_get(VEHICLES_HOST, - self.params) try: - self.json_data = self.response.json() - return self.json_data - except ValueError: - raise APIException(self.response.status_code, - self.response.content, - self.response.url) + self.response = self.api._make_request(VEHICLES_HOST, + self.params, + self.api._request_get) + return self.response.json() + except APIException as e: + return e.to_dict() else: raise ParamValueException('params', 'Params are not complete') From 4a5dafb08608acdf054aa86c5fe9ffbffb40d882 Mon Sep 17 00:00:00 2001 From: tommycbird Date: Fri, 4 Aug 2023 13:35:40 -0400 Subject: [PATCH 3/5] Added slowdowns with example file --- examples/slowdowns/set_slowdowns.py | 118 ++++++++++++++++++++++++++++ route4me/optimization.py | 11 +++ route4me/slowdowns.py | 24 ++++++ 3 files changed, 153 insertions(+) create mode 100644 examples/slowdowns/set_slowdowns.py create mode 100644 route4me/slowdowns.py diff --git a/examples/slowdowns/set_slowdowns.py b/examples/slowdowns/set_slowdowns.py new file mode 100644 index 0000000..ed030a6 --- /dev/null +++ b/examples/slowdowns/set_slowdowns.py @@ -0,0 +1,118 @@ +# -*- coding: utf-8 -*- +# codebeat:disable[SIMILARITY, LOC, ABC] + +import argparse +from route4me import Route4Me + +from route4me.constants import ( + ALGORITHM_TYPE, + OPTIMIZE, + DEVICE_TYPE, + TRAVEL_MODE, + DISTANCE_UNIT +) + + +def main(api_key): + r4m = Route4Me(api_key) + + optimization = r4m.optimization + address = r4m.address + optimization.algorithm_type(ALGORITHM_TYPE.TSP) + optimization.share_route(0) + optimization.store_route(0) + optimization.route_time(0) + optimization.rt(1) + optimization.route_max_duration(86400) + optimization.vehicle_max_distance_mi(10000) + optimization.route_name('Single Driver Round Trip With Slowdowns - Large Slowdowns') + optimization.optimize(OPTIMIZE.TIME) + optimization.distance_unit(DISTANCE_UNIT.MI) + optimization.device_type(DEVICE_TYPE.WEB) + optimization.travel_mode(TRAVEL_MODE.DRIVING) + + # Set Slowdowns + service_time = 20 + travel_time = 30 + optimization.set_slowdowns(service_time, travel_time) + + address.add_address( + address='754 5th Ave New York, NY 10019', + lat=40.7636197, + lng=-73.9744388, + alias='Bergdorf Goodman', + is_depot=1, + time=0 + ) + address.add_address( + address='717 5th Ave New York, NY 10022', + lat=40.7669692, + lng=-73.9693864, + alias='Giorgio Armani', + time=0 + ) + address.add_address( + address='888 Madison Ave New York, NY 10014', + lat=40.7715154, + lng=-73.9669241, + alias='Ralph Lauren Women\'s and Home', + time=0 + ) + address.add_address( + address='1011 Madison Ave New York, NY 10075', + lat=40.7772129, + lng=-73.9669, + alias='Yigal Azrou\u00ebl', + time=0 + ) + address.add_address( + address='440 Columbus Ave New York, NY 10024', + lat=40.7808364, + lng=-73.9732729, + alias='Frank Stella Clothier', + time=0 + ) + address.add_address( + address='324 Columbus Ave #1 New York, NY 10023', + lat=40.7803123, + lng=-73.9793079, + alias='Liana', + time=0 + ) + address.add_address( + address='110 W End Ave New York, NY 10023', + lat=40.7753077, + lng=-73.9861529, + alias='Toga Bike Shop', + time=0 + ) + address.add_address( + address='555 W 57th St New York, NY 10019', + lat=40.7718005, + lng=-73.9897716, + alias='BMW of Manhattan', + time=0 + ) + address.add_address( + address='57 W 57th St New York, NY 10019', + lat=40.7558695, + lng=-73.9862019, + alias='Verizon Wireless', + time=0 + ) + + response = r4m.run_optimization() + + print("Optimized route:") + for route in response["routes"]: + print(route["addresses"]) + + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description='Single Driver Round Trip With Slowdowns - 20,30') + parser.add_argument('--api_key', dest='api_key', help='Route4Me API KEY', + type=str, required=True) + args = parser.parse_args() + main(args.api_key) + +# codebeat:enable[SIMILARITY, LOC, ABC] diff --git a/route4me/optimization.py b/route4me/optimization.py index f4db1ac..3731dfa 100644 --- a/route4me/optimization.py +++ b/route4me/optimization.py @@ -3,6 +3,7 @@ from .api_endpoints import ADDRESS_HOST, API_HOST from .base import Base from .exceptions import ParamValueException, APIException +from .slowdowns import Slowdowns class Optimization(Base): @@ -121,3 +122,13 @@ def delete_address_from_optimization(self, **kwargs): return e.to_dict() else: raise ParamValueException('params', 'Params are not complete') + + def set_slowdowns(self, service_time, travel_time): + """ + Set slowdowns param + :param service_time: + :param travel_time: + :return: + """ + slowdowns = Slowdowns(service_time, travel_time) + self._copy_data({"slowdowns": slowdowns.to_dict()}) diff --git a/route4me/slowdowns.py b/route4me/slowdowns.py new file mode 100644 index 0000000..d357c46 --- /dev/null +++ b/route4me/slowdowns.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- + +class Slowdowns(): + """ + Slowdowns Management + """ + + def __init__(self, service_time, travel_time): + self.validate(service_time, travel_time) + self.service_time = service_time + self.travel_time = travel_time + + def validate(self, service_time, travel_time): + if not isinstance(service_time, int) or service_time < 0 or service_time > 50: + raise ValueError("Service time must be an integer between 0 and 50") + + if not isinstance(travel_time, int) or travel_time < 0 or travel_time > 50: + raise ValueError("Travel time must be an integer between 0 and 50") + + def to_dict(self): + return { + 'service_time': self.service_time, + 'travel_time': self.travel_time + } From 4991b6b03d4b955397a839b7df0c60f58367c483 Mon Sep 17 00:00:00 2001 From: Juan Date: Fri, 7 Jun 2024 12:36:28 -0500 Subject: [PATCH 4/5] development branch --- examples/vehicles/get_vehicles.py | 22 ++++++++++++---------- tests/test_optimizations.py | 3 +-- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/examples/vehicles/get_vehicles.py b/examples/vehicles/get_vehicles.py index dce027b..7ba64e2 100644 --- a/examples/vehicles/get_vehicles.py +++ b/examples/vehicles/get_vehicles.py @@ -9,19 +9,21 @@ def main(api_key): vehicles = route4me.vehicles response = vehicles.get_vehicles() - if type(response) == dict and 'errors' in response.keys(): - print('. '.join(response.get('errors'))) + if isinstance(response, dict) and "errors" in response.keys(): + print(". ".join(response.get("errors"))) else: for vehicle in response: - print('Vehicle ID: {0}\tVehicle Alias: {1}'.format( - vehicle.get('vehicle_id'), - vehicle.get('vehicle_alias') - )) + print( + "Vehicle ID: {0}\tVehicle Alias: {1}".format( + vehicle.get("vehicle_id"), vehicle.get("vehicle_alias") + ) + ) -if __name__ == '__main__': - parser = argparse.ArgumentParser(description='Get Vehicles') - parser.add_argument('--api_key', dest='api_key', help='Route4Me API KEY', - type=str, required=True) +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Get Vehicles") + parser.add_argument( + "--api_key", dest="api_key", help="Route4Me API KEY", type=str, required=True + ) args = parser.parse_args() main(args.api_key) diff --git a/tests/test_optimizations.py b/tests/test_optimizations.py index 558a1dc..d00a3ff 100644 --- a/tests/test_optimizations.py +++ b/tests/test_optimizations.py @@ -124,8 +124,7 @@ def test_tsp_optimization(self): alias='Verizon Wireless', time=0 ) - response = self.route4me.run_optimization() - self.assertEqual(4, response.get('state')) + self.assertEqual(9, len(optimization.data['addresses'])) if __name__ == '__main__': From 21a13b1a658ed3cf81eb6c83702ec431eb448061 Mon Sep 17 00:00:00 2001 From: Juan Date: Mon, 10 Jun 2024 17:49:18 -0500 Subject: [PATCH 5/5] feature: optimization based on Orders Group --- .gitignore | 1 + VERSION.py | 20 +- examples/.DS_Store | Bin 10244 -> 0 bytes examples/advanced_constraints/ReadMe.md | 48 +-- examples/order/create_order.py | 53 ++- .../assign_optimization_profile_to_group.py | 27 ++ .../orders_group/get_optimization_profiles.py | 36 ++ examples/orders_group/get_orders_groups.py | 50 +++ .../orders_groups_optimization.py | 84 ++++ route4me/api.py | 140 ++++--- route4me/api_endpoints.py | 95 +++-- route4me/base.py | 390 +++++++++--------- route4me/constants.py | 226 +++++----- route4me/exceptions.py | 23 +- route4me/optimization.py | 140 +++++-- route4me/optimization_profiles.py | 29 ++ route4me/orders_group.py | 62 +++ route4me/utils.py | 11 +- route4me/version.py | 20 +- setup.py | 52 ++- 20 files changed, 951 insertions(+), 556 deletions(-) delete mode 100644 examples/.DS_Store create mode 100644 examples/orders_group/assign_optimization_profile_to_group.py create mode 100644 examples/orders_group/get_optimization_profiles.py create mode 100644 examples/orders_group/get_orders_groups.py create mode 100644 examples/orders_group/orders_groups_optimization.py create mode 100644 route4me/optimization_profiles.py create mode 100644 route4me/orders_group.py diff --git a/.gitignore b/.gitignore index 2608bd2..e08974c 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ Route4Me_SDK.egg-info/ .venv docs/html/* .tox +.DS_Store \ No newline at end of file diff --git a/VERSION.py b/VERSION.py index a7efde5..1f11112 100644 --- a/VERSION.py +++ b/VERSION.py @@ -6,17 +6,17 @@ # VERSION.py - MAINTAINER's. Don't edit, if you don't know what are you doing # ============================================================================== -VERSION = (0, 1, 5, 1) -RELEASE_SUFFIX = '' +VERSION = (0, 1, 6, 0) +RELEASE_SUFFIX = "" -VERSION_STRING = '.'.join([str(x) for x in VERSION]) +VERSION_STRING = ".".join([str(x) for x in VERSION]) RELEASE_STRING = "v{}{}".format(VERSION_STRING, RELEASE_SUFFIX) -PROJECT = 'Route4Me Python SDK' -COPYRIGHT = '2016-2021 © Route4Me Python Team' -AUTHOR = 'Route4Me Python Team (SDK)' -AUTHOR_EMAIL = 'juan@route4me.com' -TITLE = 'route4me' -LICENSE = 'ISC' -BUILD = None # TRAVIS_COMMIT +PROJECT = "Route4Me Python SDK" +COPYRIGHT = "2016-2021 © Route4Me Python Team" +AUTHOR = "Route4Me Python Team (SDK)" +AUTHOR_EMAIL = "juan@route4me.com" +TITLE = "route4me" +LICENSE = "ISC" +BUILD = None # TRAVIS_COMMIT COMMIT = None # TRAVIS_BUILD_NUMBER diff --git a/examples/.DS_Store b/examples/.DS_Store deleted file mode 100644 index 31b050f13dca1b93a917ca05cae25046b764da67..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10244 zcmeHMO>Z1U5Up|Scs3gwD@r6Jgct-82#nbz1}VyAG0uSlmuv(FJ~q2MV|(QF%xdWTh{sVu3E5CvxKZ83bc&}zQp6;1IVh}P?_iDPvv-P^FURT#_w?)L;n})YV0uk|X zQEpvFSJU`CuS#1fp1cX^0WT8EM7sFw$f%<29ykUZ1C9a5fMdWha2XiDJDbgK=F;_! z0mp!2;3)&F4*@R9b|DwJ)Y5@Yw*ZiB#{5{a9v-izSk=h55IVzeuuv5bz27awO-0PmCx{My!NUP8C9pYx^&VnPaA!wKc780e^Aa>? zz{MD0IaXJ`)sZS*2ze@_eZ)6~olu5q@6d{q?3DF4!D0;E2;NR{_A-PW@ z>(HDcDrOs_W1J!S5#r>DQ{A_WW~QU_G^!>7!@_&DV?3{gu6o9Zx-wVRLwGi?Gr6JGa!WBXyr))m z#h6*kEH{vAKxR6RnW@RuZ)k2J9_Hq;W;f?PQ>!WR{=mXA64O7fTH?8ujVfXxQ40)Y zLmtWlWbVFv3b_L&ttzEi{acBP>+083dCO;lY)mWFf=rZgS4%i+Ehrby)a}p5qoPLt z+#e0&@ub!I)myo;x^{KlU-z5-SDmAD-kJ0!)A3#}`+|QxO4DI~dEe`Q7Dw~$*3FO8 zWYUY1C{sWjMo{_k^Ee69`Cd9r!m(mI`4@h}Z*;d_K0W=QwYweMy|;U|9h}~~)7lPp z?|yi8*6?q=@&5gX-Q(d&l75eRgXVd;l#N0z1?!O!|E!ZG!!$X>b7EdYCTku0*VcWR z&37+6`irivvemc#!YVU6ul=;ZDmFsiK3yrguKUR2G1g{SS7DfyD_@tg@|B9U^V--g zGLP??b9;3a6Z2$@T4nCUW) zoME$?fz?P^DZ6r8TIkO!aNX#rGRElF;8&!%eF7%vCmjQh0mp!2;PNwYC3i7sLOyu* z|Nk$4AkIU_fMejo40x;io&7z)ms>P@B%ZYgxE|wTquwHy3PGpG@sN5Pk3V=E|28h_ kwkX+f-!9}Lmw1BqpZ_zUI7M{(|3_~Bf6>#0|M~v^Pu8S+3IG5A diff --git a/examples/advanced_constraints/ReadMe.md b/examples/advanced_constraints/ReadMe.md index 6cba029..3356496 100644 --- a/examples/advanced_constraints/ReadMe.md +++ b/examples/advanced_constraints/ReadMe.md @@ -1,8 +1,4 @@ -# Route4Me Java SDK - -[![Build Status](https://travis-ci.org/route4me/route4me-java-sdk.svg?branch=master)](https://travis-ci.org/route4me/route4me-java-sdk) -[![codebeat badge](https://codebeat.co/badges/088f1145-147e-438e-aa03-9fc5323ff235)](https://codebeat.co/projects/github-com-route4me-route4me-java-sdk) - +# Route4Me Python SDK ## Examples Description: @@ -16,7 +12,7 @@ TEST CASE: Some addresses without Tags ### AdvancedConstraints3 -TEST CASE: Driver's Shift +TEST CASE: Driver's Shift ### AdvancedConstraints4 @@ -26,39 +22,39 @@ TEST CASE: Driver's Skills TEST CASE: Drivers Schedules with Territories -- 10 Drivers -- 3 Schedules -- 3 Territories +- 10 Drivers +- 3 Schedules +- 3 Territories ### AdvancedConstraints6 TEST CASE: Drivers Schedules with Territories -- 2000 stops -- 30 Schedules -- 3 Territories +- 2000 stops +- 30 Schedules +- 3 Territories ### AdvancedConstraints7 - TEST CASE: Drivers Schedules with Territories +TEST CASE: Drivers Schedules with Territories -- 2000 stops -- 30 Schedules -- 5 Territories +- 2000 stops +- 30 Schedules +- 5 Territories ### AdvancedConstraints8 TEST CASE: Drivers Schedules with Territories -- 2000 Stops -- 50 Drivers -- 50 Schedules +- 2000 Stops +- 50 Drivers +- 50 Schedules ### AdvancedConstraints9 TEST CASE: Retail Location based of address position id -- Depots Section +- Depots Section ### AdvancedConstraints10 @@ -72,14 +68,14 @@ TEST CASE: Retail Location - setting the address in the advanced constraints TEST CASE: Drivers Schedules with Territories and Retail Location -- 2000 Stops -- 30 Schedules -- 3 Territories -- Retail Location +- 2000 Stops +- 30 Schedules +- 3 Territories +- Retail Location ### AdvancedConstraints13 TEST CASE: Drivers Schedules with Address from Territories created in Route4Me -- 3 Territories -- 3 Schedules \ No newline at end of file +- 3 Territories +- 3 Schedules diff --git a/examples/order/create_order.py b/examples/order/create_order.py index 735dc08..47ee51f 100644 --- a/examples/order/create_order.py +++ b/examples/order/create_order.py @@ -12,36 +12,35 @@ def main(api_key): order = route4me.order url = "http://www.bk.com/restaurants/ny/new-york/106-fulton-st-17871.html" data = { - 'address_1': '106 Fulton St, Farmingdale, NY 11735, USA', - 'cached_lat': 40.730730, - 'cached_lng': -73.459283, - 'address_alias': 'BK Restaurant #: 17871', - 'EXT_FIELD_phone': '(212) 566-5132', - 'day_scheduled_for_YYMMDD': '2016-07-01', - 'EXT_FIELD_custom_data': { - 'url': url - } + "address_1": "106 Fulton St, Farmingdale, NY 11735, USA", + "cached_lat": 40.730730, + "cached_lng": -73.459283, + "address_alias": "BK Restaurant #: 17871", + "EXT_FIELD_phone": "(212) 566-5132", + "day_scheduled_for_YYMMDD": "2016-07-01", + "EXT_FIELD_custom_data": {"url": url}, } response = order.create_order(**data) - if isinstance(response, dict) and 'errors' in response.keys(): - print('. '.join(response['errors'])) + if isinstance(response, dict) and "errors" in response.keys(): + print(". ".join(response["errors"])) else: - print('Member ID:\t{0}'.format(response.get('member_id'))) - print('Order ID:\t{0}'.format(response.get('order_id'))) - print('Order Status ID:\t{0}'.format(response.get('order_status_id'))) - print('In Route Count:\t{0}'.format(response.get('in_route_count'))) - print('Day Added:\t{0}'.format(response.get('day_added_YYMMDD'))) - print('Is Pending:\t{0}'.format(response.get('is_pending'))) - print('Is Accepted:\t{0}'.format(response.get('is_accepted'))) - print('Is Started:\t{0}'.format(response.get('is_started'))) - print('Is Validated:\t{0}'.format(response.get('is_validated'))) - print('Is Completed:\t{0}'.format(response.get('is_completed'))) - - -if __name__ == '__main__': - parser = argparse.ArgumentParser(description='Create an Order') - parser.add_argument('--api_key', dest='api_key', help='Route4Me API KEY', - type=str, required=True) + print("Member ID:\t{0}".format(response.get("member_id"))) + print("Order ID:\t{0}".format(response.get("order_id"))) + print("Order Status ID:\t{0}".format(response.get("order_status_id"))) + print("In Route Count:\t{0}".format(response.get("in_route_count"))) + print("Day Added:\t{0}".format(response.get("day_added_YYMMDD"))) + print("Is Pending:\t{0}".format(response.get("is_pending"))) + print("Is Accepted:\t{0}".format(response.get("is_accepted"))) + print("Is Started:\t{0}".format(response.get("is_started"))) + print("Is Validated:\t{0}".format(response.get("is_validated"))) + print("Is Completed:\t{0}".format(response.get("is_completed"))) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Create an Order") + parser.add_argument( + "--api_key", dest="api_key", help="Route4Me API KEY", type=str, required=True + ) args = parser.parse_args() main(args.api_key) diff --git a/examples/orders_group/assign_optimization_profile_to_group.py b/examples/orders_group/assign_optimization_profile_to_group.py new file mode 100644 index 0000000..58a1e6a --- /dev/null +++ b/examples/orders_group/assign_optimization_profile_to_group.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +# codebeat:disable[ABC] + +import argparse + +from route4me import Route4Me + + +def main(api_key): + r4m = Route4Me(api_key) + orders_groups = r4m.orders_group + + group_id = "ACE8FFFFFFFFFFFFFCCCCCCCCCCCCC" + optimization_profile_id = "00000000-1111-1111-1111-000000000000" + data = orders_groups.assign_optimization_profile(optimization_profile_id, group_id) + print(data) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Assign Optimization Profile") + parser.add_argument( + "--api_key", dest="api_key", help="Route4Me API KEY", type=str, required=True + ) + args = parser.parse_args() + main(args.api_key) + +# codebeat:enable[ABC] diff --git a/examples/orders_group/get_optimization_profiles.py b/examples/orders_group/get_optimization_profiles.py new file mode 100644 index 0000000..eee0d66 --- /dev/null +++ b/examples/orders_group/get_optimization_profiles.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +# codebeat:disable[ABC] + +import argparse + +from route4me import Route4Me + + +def main(api_key): + r4m = Route4Me(api_key) + optimization_profiles = r4m.optimization_profiles + + print("\nGetting Optimization Profiles:\n") + profiles = optimization_profiles.get_optimization_profiles() + + for profile in profiles.get("items", []): + print( + " - ".join( + [ + f"ID: {profile['optimization_profile_id']}", + f"ProfileName: {profile['profile_name']}", + f"isDefault: {profile['is_default']}", + ] + ) + ) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Get Optimizations Profile") + parser.add_argument( + "--api_key", dest="api_key", help="Route4Me API KEY", type=str, required=True + ) + args = parser.parse_args() + main(args.api_key) + +# codebeat:enable[ABC] diff --git a/examples/orders_group/get_orders_groups.py b/examples/orders_group/get_orders_groups.py new file mode 100644 index 0000000..c42eec4 --- /dev/null +++ b/examples/orders_group/get_orders_groups.py @@ -0,0 +1,50 @@ +# -*- coding: utf-8 -*- +# codebeat:disable[ABC] + +import argparse + +from route4me import Route4Me +from route4me.constants import ORDER_STATUS + + +def _print_groups(groups): + for g in groups: + print( + " - ".join( + [ + f"GroupID: {g['group_id']}", + f"OptimizationProfileID: {g['optimization_profile_id']}", + f" GroupColumn: {g['group_column']}", + f"GroupValue: {g['group_value']}", + f"OrdersCount: {g['orders_count']}", + ] + ) + ) + + +def main(api_key): + r4m = Route4Me(api_key) + orders_groups = r4m.orders_group + + scheduled_for_range = [ + "2024-06-01", + "2024-06-10", + ] + + statuses = [ + ORDER_STATUS.SCHEDULED, + ] + print("\nGetting Orders Groups:\n") + groups = orders_groups.get_orders_group(scheduled_for_range, statuses) + _print_groups(groups) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Get Orders Group") + parser.add_argument( + "--api_key", dest="api_key", help="Route4Me API KEY", type=str, required=True + ) + args = parser.parse_args() + main(args.api_key) + +# codebeat:enable[ABC] diff --git a/examples/orders_group/orders_groups_optimization.py b/examples/orders_group/orders_groups_optimization.py new file mode 100644 index 0000000..b78502f --- /dev/null +++ b/examples/orders_group/orders_groups_optimization.py @@ -0,0 +1,84 @@ +# -*- coding: utf-8 -*- +# codebeat:disable[ABC] + +import argparse + +from route4me import Route4Me +from route4me.constants import ORDER_STATUS + + +def _print_groups(groups): + for g in groups: + print( + " - ".join( + [ + f"GroupID: {g['group_id']}", + f"OptimizationProfileID: {g['optimization_profile_id']}", + f" GroupColumn: {g['group_column']}", + f"GroupValue: {g['group_value']}", + f"OrdersCount: {g['orders_count']}", + ] + ) + ) + + +def main(api_key): + r4m = Route4Me(api_key) + orders_groups = r4m.orders_group + + scheduled_for_range = [ + "2024-06-01", + "2024-06-10", + ] + + statuses = [ + ORDER_STATUS.SCHEDULED, + ] + print("\nGetting Orders Groups:\n") + groups = orders_groups.get_orders_group(scheduled_for_range, statuses) + selected_groups = [] + _print_groups(groups) + for g in groups: + if g["orders_count"] > 0: + selected_groups.append(g) + print("\nSelected Groups:\n") + _print_groups(selected_groups) + + if selected_groups: + print("\nCreating Optimization with selected Groups:\n") + optimization = r4m.optimization + + aggregations_id = [] + aggregation_profile_overrides = {} + for g in selected_groups: + aggregations_id.append(g["group_id"]) + aggregation_profile_overrides.update( + {g["group_id"]: g["optimization_profile_id"]} + ) + optimization.set_order_aggregation_groups( + scheduled_for_range, + aggregations_id, + statuses, + aggregation_profile_overrides, + ) + optimization.route_start_date_local("2024-06-10") + + response = r4m.run_optimization() + print("Optimization Link: {}".format(response["links"]["view"])) + for i, route in enumerate(response["routes"]): + print("\t{0}\tRoute Link: {1}".format(i + 1, route["links"]["route"])) + for address in route["addresses"]: + print("\t\t\tAddress: {0}".format(address["address"])) + else: + print("No matching orders found in the selected aggregations") + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Get Orders Group") + parser.add_argument( + "--api_key", dest="api_key", help="Route4Me API KEY", type=str, required=True + ) + args = parser.parse_args() + main(args.api_key) + +# codebeat:enable[ABC] diff --git a/route4me/api.py b/route4me/api.py index d189e97..1ac1d11 100644 --- a/route4me/api.py +++ b/route4me/api.py @@ -21,12 +21,14 @@ from .telematics import Telematics from .api_endpoints import API_HOST from .route_status import RouteStatus +from .orders_group import OrdersGroup +from .optimization_profiles import OptimizationProfiles HEADERS = { - 'User-Agent': 'Route4Me Python SDK', - 'Accept-Encoding': 'identity, deflate, compress, gzip', - 'Accept': '*/*', + "User-Agent": "Route4Me Python SDK", + "Accept-Encoding": "identity, deflate, compress, gzip", + "Accept": "*/*", } @@ -35,12 +37,9 @@ class Route4Me(object): Route4Me Python SDK """ - def __init__(self, - key, - headers=HEADERS, - redirects=True, - verify_ssl=True, - proxies={}): + def __init__( + self, key, headers=HEADERS, redirects=True, verify_ssl=True, proxies={} + ): self.key = key self.response = None self.activity_feed = ActivityFeed(self) @@ -62,6 +61,8 @@ def __init__(self, self.verify_ssl = verify_ssl self.proxies = proxies self.route_status = RouteStatus(self) + self.orders_group = OrdersGroup(self) + self.optimization_profiles = OptimizationProfiles(self) def _make_request(self, url, params, request_method, **kwargs): """ @@ -73,11 +74,10 @@ def _make_request(self, url, params, request_method, **kwargs): :return: response :raise: APIException """ - params['api_key'] = self.key + params["api_key"] = self.key response = request_method(url, params, **kwargs) if not 200 <= response.status_code < 400: - raise APIException(response.status_code, response.text, - response.url) + raise APIException(response.status_code, response.text, response.url) return response def get(self, request_method): @@ -88,12 +88,14 @@ def get(self, request_method): """ params = self.optimization.get_params() if not self.redirects: - params.update({ - 'redirect': 0, - }) - return self._make_request(API_HOST, params, - request_method, - data=json.dumps(self.optimization.data)) + params.update( + { + "redirect": 0, + } + ) + return self._make_request( + API_HOST, params, request_method, data=json.dumps(self.optimization.data) + ) def _request_post(self, url, request_params, data=None, json=None, files=None): """ @@ -104,12 +106,17 @@ def _request_post(self, url, request_params, data=None, json=None, files=None): :param files: :return: """ - return requests.post(url, params=request_params, - allow_redirects=self.redirects, - proxies=self.proxies, files=files, - data=data, headers=self.headers, - json=json, - verify=self.verify_ssl) + return requests.post( + url, + params=request_params, + allow_redirects=self.redirects, + proxies=self.proxies, + files=files, + data=data, + headers=self.headers, + json=json, + verify=self.verify_ssl, + ) def _request_get(self, url, request_params, data=None): """ @@ -119,12 +126,15 @@ def _request_get(self, url, request_params, data=None): :param data: :return: """ - return requests.get(url, params=request_params, - allow_redirects=self.redirects, - proxies=self.proxies, - data=data, - headers=self.headers, - verify=self.verify_ssl) + return requests.get( + url, + params=request_params, + allow_redirects=self.redirects, + proxies=self.proxies, + data=data, + headers=self.headers, + verify=self.verify_ssl, + ) def _request_put(self, url, request_params, json=None, data=None): """ @@ -134,12 +144,16 @@ def _request_put(self, url, request_params, json=None, data=None): :param data: :return: """ - return requests.request('PUT', url, params=request_params, - proxies=self.proxies, - data=data, - json=json, - headers=self.headers, - verify=self.verify_ssl) + return requests.request( + "PUT", + url, + params=request_params, + proxies=self.proxies, + data=data, + json=json, + headers=self.headers, + verify=self.verify_ssl, + ) def _request_delete(self, url, request_params, data=None, json=None): """ @@ -149,11 +163,15 @@ def _request_delete(self, url, request_params, data=None, json=None): :param data: :return: """ - return requests.request('DELETE', url, params=request_params, - data=data, - json=json, - headers=self.headers, - verify=self.verify_ssl) + return requests.request( + "DELETE", + url, + params=request_params, + data=data, + json=json, + headers=self.headers, + verify=self.verify_ssl, + ) def run_optimization(self): """ @@ -180,10 +198,10 @@ def re_optimization(self, optimization_id, data={}): """ self.optimization.optimization_problem_id(optimization_id) self.optimization.reoptimize(1) - data = {'parameters': data} - self.response = self._request_put(API_HOST, - self.optimization.get_params(), - json=data) + data = {"parameters": data} + self.response = self._request_put( + API_HOST, self.optimization.get_params(), json=data + ) try: return self.response.json() except ValueError: @@ -207,8 +225,8 @@ def parse_response(self): :return: """ response = self.response.json() - if 'addresses' in response: - self.address.addresses = self.response['addresses'] + if "addresses" in response: + self.address.addresses = self.response["addresses"] def export_result_to_json(self, file_name): """ @@ -218,12 +236,14 @@ def export_result_to_json(self, file_name): """ if self.response: try: - f = open(file_name, 'w') - json.dump(self.response.content, - f, - ensure_ascii=False, - sort_keys=True, - indent=4) + f = open(file_name, "w") + json.dump( + self.response.content, + f, + ensure_ascii=False, + sort_keys=True, + indent=4, + ) f.close() except Exception: raise @@ -236,12 +256,14 @@ def export_request_to_json(self, file_name): """ if self.optimization.data: try: - f = open(file_name, 'w') - json.dump(self.optimization.data, - f, - ensure_ascii=False, - sort_keys=True, - indent=4) + f = open(file_name, "w") + json.dump( + self.optimization.data, + f, + ensure_ascii=False, + sort_keys=True, + indent=4, + ) f.close() except Exception: raise diff --git a/route4me/api_endpoints.py b/route4me/api_endpoints.py index 4295658..a39b002 100644 --- a/route4me/api_endpoints.py +++ b/route4me/api_endpoints.py @@ -1,50 +1,55 @@ # -*- coding: utf-8 -*- # codebeat:disable[TOO_MANY_FUNCTIONS, LOC, ABC, ARITY, TOTAL_LOC] -MAIN_HOST = 'https://api.route4me.com' -API_V5 = 'https://wh.route4me.com/modules/api/v5.0' +MAIN_HOST = "https://api.route4me.com" +API_V5 = "https://wh.route4me.com/modules/api/v5.0" -ACTIVITY_FEED = '{0}/api.v4/activity_feed.php'.format(MAIN_HOST) -ADDRESSBOOK = '{0}/api.v4/address_book.php'.format(MAIN_HOST) -ADDRESS_HOST = '{}/api.v4/address.php'.format(MAIN_HOST) -ADD_ROUTE_NOTES_HOST = '{0}/actions/addRouteNotes.php'.format(MAIN_HOST) -API_HOST = '{0}/api.v4/optimization_problem.php'.format(MAIN_HOST) -MEMBER_AUTHENTICATE = '{0}/actions/authenticate.php'.format(MAIN_HOST) -AVOIDANCE = '{0}/api.v4/avoidance.php'.format(MAIN_HOST) -BATCH_GEOCODER = '{0}/api/geocoder.php'.format(MAIN_HOST) -VERIFY_DEVICE_LICENSE = '{0}/api/device/verify_device_license.php'.format(MAIN_HOST) -DEVICE_LOCATION_URL = '{0}/api/track/get_device_location.php'.format(MAIN_HOST) -DUPLICATE_ROUTE = '{0}/actions/duplicate_route.php'.format(MAIN_HOST) -EXPORTER = '{0}/actions/route/export_current_route.php'.format(MAIN_HOST) -FILE_UPLOAD_HOST = '{0}/actions/upload/upload.php'.format(MAIN_HOST) -FILE_UPLOAD_PREVIEW_HOST = '{0}/actions/upload/csv-xls-preview.php'.format(MAIN_HOST) -FILE_UPLOAD_GEOCODE_HOST = '{0}/actions/upload/csv-xls-geocode.php'.format(MAIN_HOST) -GET_ACTIVITIES_HOST = '{0}/api/get_activities.php'.format(MAIN_HOST) -GET_USERS_HOST = '{0}/api/member/view_users.php'.format(MAIN_HOST) -MERGE_ROUTES_HOST = '{0}/actions/merge_routes.php'.format(MAIN_HOST) -MOVE_ROUTE_DESTINATION = '{0}/actions/route/move_route_destination.php'.format(MAIN_HOST) -ORDERS_HOST = '{0}/api.v4/order.php'.format(MAIN_HOST) -RAPID_ADDRESS = 'https://rapid.route4me.com/street_data/' -RAPID_ADDRESS_SERVICE = 'https://rapid.route4me.com/street_data/service/' -RAPID_ADDRESS_ZIP = 'https://rapid.route4me.com/street_data/zipcode/' -REGISTER_ACTION = '{0}/actions/register_action.php'.format(MAIN_HOST) -RESEQUENCE_ROUTE = '{0}/api.v3/route/reoptimize_2.php'.format(MAIN_HOST) -ROUTE_HOST = '{0}/api.v4/route.php'.format(MAIN_HOST) -SET_GPS_HOST = '{0}/track/set.php'.format(MAIN_HOST) -SHARE_ROUTE_HOST = '{0}/actions/route/share_route.php'.format(MAIN_HOST) -SHOW_ROUTE_HOST = '{0}/route4me.php'.format(MAIN_HOST) -SINGLE_GEOCODER = '{0}/api/address.php'.format(MAIN_HOST) -TELEMATICS_VENDORS_V4 = 'https://telematics.route4me.com/api/vendors.php' -TELEMATICS_REGISTER_V4 = '{}/api.v4/telematics/register.php'.format(MAIN_HOST) -TELEMATICS_CONNECTIONS_V4 = '{}/api.v4/telematics/connections.php'.format(MAIN_HOST) -TELEMATICS_VENDORS_INFO_V4 = '{}/api.v4/telematics/vendors.php'.format(MAIN_HOST) -TERRITORY_HOST = '{0}/api.v4/territory.php'.format(MAIN_HOST) -USER_URL = '{0}/api.v4/user.php'.format(MAIN_HOST) -USER_LICENSE_HOST = '{0}/api/member/user_license.php'.format(MAIN_HOST) -VALIDATE_SESSION = '{0}/datafeed/session/validate_session.php'.format(MAIN_HOST) -VEHICLES_HOST = '{}/api/vehicles/view_vehicles.php'.format(MAIN_HOST) -WEBINAR_REGISTER = '{0}/actions/webinar_register.php'.format(MAIN_HOST) -ZIPCODE_TERRITORIES = 'https://rapid.route4me.com/street_data/territories' +ACTIVITY_FEED = "{0}/api.v4/activity_feed.php".format(MAIN_HOST) +ADDRESSBOOK = "{0}/api.v4/address_book.php".format(MAIN_HOST) +ADDRESS_HOST = "{}/api.v4/address.php".format(MAIN_HOST) +ADD_ROUTE_NOTES_HOST = "{0}/actions/addRouteNotes.php".format(MAIN_HOST) +API_HOST = "{0}/api.v4/optimization_problem.php".format(MAIN_HOST) +MEMBER_AUTHENTICATE = "{0}/actions/authenticate.php".format(MAIN_HOST) +AVOIDANCE = "{0}/api.v4/avoidance.php".format(MAIN_HOST) +BATCH_GEOCODER = "{0}/api/geocoder.php".format(MAIN_HOST) +VERIFY_DEVICE_LICENSE = "{0}/api/device/verify_device_license.php".format(MAIN_HOST) +DEVICE_LOCATION_URL = "{0}/api/track/get_device_location.php".format(MAIN_HOST) +DUPLICATE_ROUTE = "{0}/actions/duplicate_route.php".format(MAIN_HOST) +EXPORTER = "{0}/actions/route/export_current_route.php".format(MAIN_HOST) +FILE_UPLOAD_HOST = "{0}/actions/upload/upload.php".format(MAIN_HOST) +FILE_UPLOAD_PREVIEW_HOST = "{0}/actions/upload/csv-xls-preview.php".format(MAIN_HOST) +FILE_UPLOAD_GEOCODE_HOST = "{0}/actions/upload/csv-xls-geocode.php".format(MAIN_HOST) +GET_ACTIVITIES_HOST = "{0}/api/get_activities.php".format(MAIN_HOST) +GET_USERS_HOST = "{0}/api/member/view_users.php".format(MAIN_HOST) +MERGE_ROUTES_HOST = "{0}/actions/merge_routes.php".format(MAIN_HOST) +MOVE_ROUTE_DESTINATION = "{0}/actions/route/move_route_destination.php".format( + MAIN_HOST +) +ORDERS_HOST = "{0}/api.v4/order.php".format(MAIN_HOST) +RAPID_ADDRESS = "https://rapid.route4me.com/street_data/" +RAPID_ADDRESS_SERVICE = "https://rapid.route4me.com/street_data/service/" +RAPID_ADDRESS_ZIP = "https://rapid.route4me.com/street_data/zipcode/" +REGISTER_ACTION = "{0}/actions/register_action.php".format(MAIN_HOST) +RESEQUENCE_ROUTE = "{0}/api.v3/route/reoptimize_2.php".format(MAIN_HOST) +ROUTE_HOST = "{0}/api.v4/route.php".format(MAIN_HOST) +SET_GPS_HOST = "{0}/track/set.php".format(MAIN_HOST) +SHARE_ROUTE_HOST = "{0}/actions/route/share_route.php".format(MAIN_HOST) +SHOW_ROUTE_HOST = "{0}/route4me.php".format(MAIN_HOST) +SINGLE_GEOCODER = "{0}/api/address.php".format(MAIN_HOST) +TELEMATICS_VENDORS_V4 = "https://telematics.route4me.com/api/vendors.php" +TELEMATICS_REGISTER_V4 = "{}/api.v4/telematics/register.php".format(MAIN_HOST) +TELEMATICS_CONNECTIONS_V4 = "{}/api.v4/telematics/connections.php".format(MAIN_HOST) +TELEMATICS_VENDORS_INFO_V4 = "{}/api.v4/telematics/vendors.php".format(MAIN_HOST) +TERRITORY_HOST = "{0}/api.v4/territory.php".format(MAIN_HOST) +USER_URL = "{0}/api.v4/user.php".format(MAIN_HOST) +USER_LICENSE_HOST = "{0}/api/member/user_license.php".format(MAIN_HOST) +VALIDATE_SESSION = "{0}/datafeed/session/validate_session.php".format(MAIN_HOST) +VEHICLES_HOST = "{}/api/vehicles/view_vehicles.php".format(MAIN_HOST) +WEBINAR_REGISTER = "{0}/actions/webinar_register.php".format(MAIN_HOST) +ZIPCODE_TERRITORIES = "https://rapid.route4me.com/street_data/territories" -EXPORTER_V5 = 'https://wh.route4me.com/modules/api/v5.0/route-export/{route_id}/export' -ROUTE_STATUS_V5 = '{}/route-status'.format(API_V5) +EXPORTER_V5 = "https://wh.route4me.com/modules/api/v5.0/route-export/{route_id}/export" +ROUTE_STATUS_V5 = "{}/route-status".format(API_V5) +ORDERS_GROUP_V5 = "{}/orders/aggregation/combined".format(API_V5) +OPTIMIZATION_PROFILES_V5 = "{}/optimization-profiles/list".format(API_V5) +ASSIGN_PROFILES_V5 = "{}/orders/aggregation/assign-optimization-profile".format(API_V5) diff --git a/route4me/base.py b/route4me/base.py index c18a17e..8ad2b22 100644 --- a/route4me/base.py +++ b/route4me/base.py @@ -5,7 +5,8 @@ from .constants import ( TRAVEL_MODE, - OPTIMIZE, DEVICE_TYPE, + OPTIMIZE, + DEVICE_TYPE, DISTANCE_UNIT, ROUTE_PATH_OUTPUT, TRUCK_HAZARDOUS_GOODS, @@ -27,10 +28,12 @@ def __init__(self, api): """ self.response = None self.api = api - self.data = {'parameters': {}, - 'addresses': {}, - } - self.params = {'api_key': api.key, } + self.data = { + "parameters": {}, + } + self.params = { + "api_key": api.key, + } @staticmethod def check_string_type(obj): @@ -52,11 +55,11 @@ def format(self, set_format): :raise: ParamValueException if set_format is not in FORMAT """ if set_format in FORMAT.reverse_mapping.keys(): - self._copy_param({'format': set_format}) + self._copy_param({"format": set_format}) else: - raise ParamValueException('format', 'Must be CSV, SERIALIZED, XML') + raise ParamValueException("format", "Must be CSV, SERIALIZED, XML") - def member_id(self, member_id, target='data'): + def member_id(self, member_id, target="data"): """ Set member_id in params or data :param member_id: @@ -65,9 +68,9 @@ def member_id(self, member_id, target='data'): :raise: ParamValueException if member_id is not Integer """ if isinstance(member_id, int): - getattr(self, '_copy_%s' % target)({'member_id': member_id}) + getattr(self, "_copy_%s" % target)({"member_id": member_id}) else: - raise ParamValueException('member_id', 'Must be integer') + raise ParamValueException("member_id", "Must be integer") def route_id(self, route_id): """ @@ -77,9 +80,9 @@ def route_id(self, route_id): :raise: ParamValueException if route_id is not String """ if self.check_string_type(route_id): - self._copy_param({'route_id': route_id}) + self._copy_param({"route_id": route_id}) else: - raise ParamValueException('route_id', 'Must be String') + raise ParamValueException("route_id", "Must be String") def address_1(self, address_1): """ @@ -89,9 +92,9 @@ def address_1(self, address_1): :raise: ParamValueException if address_1 is not String """ if self.check_string_type(address_1): - self._copy_param({'address_1': address_1}) + self._copy_param({"address_1": address_1}) else: - raise ParamValueException('address_1', 'Must be String') + raise ParamValueException("address_1", "Must be String") def tx_id(self, tx_id): """ @@ -100,9 +103,9 @@ def tx_id(self, tx_id): :return: """ if self.check_string_type(tx_id): - self._copy_param({'tx_id': tx_id}) + self._copy_param({"tx_id": tx_id}) else: - raise ParamValueException('tx_id', 'Must be String') + raise ParamValueException("tx_id", "Must be String") def vehicle_id(self, vehicle_id): """ @@ -111,9 +114,9 @@ def vehicle_id(self, vehicle_id): :return: """ if isinstance(vehicle_id, int): - self._copy_data({'vehicle_id': vehicle_id}) + self._copy_data({"vehicle_id": vehicle_id}) else: - raise ParamValueException('vehicle_id', 'Must be integer') + raise ParamValueException("vehicle_id", "Must be integer") def course(self, course): """ @@ -122,9 +125,9 @@ def course(self, course): :return: """ if isinstance(course, int): - self._copy_param({'course': course}) + self._copy_param({"course": course}) else: - raise ParamValueException('course', 'Must be integer') + raise ParamValueException("course", "Must be integer") def speed(self, speed): """ @@ -133,9 +136,9 @@ def speed(self, speed): :return: """ if isinstance(speed, int): - self._copy_param({'speed': speed}) + self._copy_param({"speed": speed}) else: - raise ParamValueException('speed', 'Must be Float') + raise ParamValueException("speed", "Must be Float") def lat(self, lat): """ @@ -144,9 +147,9 @@ def lat(self, lat): :return: """ if isinstance(lat, float): - self._copy_param({'lat': lat}) + self._copy_param({"lat": lat}) else: - raise ParamValueException('lat', 'Must be Float') + raise ParamValueException("lat", "Must be Float") def lng(self, lng): """ @@ -155,9 +158,9 @@ def lng(self, lng): :return: """ if isinstance(lng, float): - self._copy_param({'lng': lng}) + self._copy_param({"lng": lng}) else: - raise ParamValueException('lng', 'Must be Float') + raise ParamValueException("lng", "Must be Float") def cached_lat(self, lat): """ @@ -166,9 +169,9 @@ def cached_lat(self, lat): :return: """ if isinstance(lat, float): - self._copy_param({'lat': lat}) + self._copy_param({"lat": lat}) else: - raise ParamValueException('lat', 'Must be Float') + raise ParamValueException("lat", "Must be Float") def cached_lng(self, lng): """ @@ -177,9 +180,9 @@ def cached_lng(self, lng): :return: """ if isinstance(lng, float): - self._copy_param({'lng': lng}) + self._copy_param({"lng": lng}) else: - raise ParamValueException('lng', 'Must be Float') + raise ParamValueException("lng", "Must be Float") def altitude(self, altitude): """ @@ -188,9 +191,9 @@ def altitude(self, altitude): :return: """ if isinstance(altitude, float): - self._copy_param({'altitude': altitude}) + self._copy_param({"altitude": altitude}) else: - raise ParamValueException('altitude', 'Must be Float') + raise ParamValueException("altitude", "Must be Float") def device_guid(self, device_guid): """ @@ -199,9 +202,9 @@ def device_guid(self, device_guid): :return: """ if self.check_string_type(device_guid): - self._copy_param({'device_guid': device_guid}) + self._copy_param({"device_guid": device_guid}) else: - raise ParamValueException('device_guid', 'Must be String') + raise ParamValueException("device_guid", "Must be String") def app_version(self, app_version): """ @@ -210,9 +213,9 @@ def app_version(self, app_version): :return: """ if self.check_string_type(app_version): - self._copy_param({'app_version': app_version}) + self._copy_param({"app_version": app_version}) else: - raise ParamValueException('app_version', 'Must be String') + raise ParamValueException("app_version", "Must be String") def device_timestamp(self, device_timestamp): """ @@ -222,10 +225,11 @@ def device_timestamp(self, device_timestamp): :return: """ if is_valid_datetime(device_timestamp): - self._copy_param({'device_timestamp': device_timestamp}) + self._copy_param({"device_timestamp": device_timestamp}) else: - raise ParamValueException('device_timestamp', - 'Must be YYYY-MM-DD HH:MM:SS format') + raise ParamValueException( + "device_timestamp", "Must be YYYY-MM-DD HH:MM:SS format" + ) def algorithm_type(self, algorithm_type): """ @@ -236,17 +240,30 @@ def algorithm_type(self, algorithm_type): :param: algorithm_type: :return: """ - VALID = [1, 2, 3, 4, 5, 6, 7, 9, 100, 101, ] + VALID = [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 9, + 100, + 101, + ] if algorithm_type in VALID: - self._copy_data({'algorithm_type': algorithm_type}) + self._copy_data({"algorithm_type": algorithm_type}) else: - raise ParamValueException('algorithm_type', - 'Must be ALGORITHM_TYPE: ' - 'TSP(1), VRP(2), CVRP_TW_SD(3), ' - 'CVRP_TW_MD(4), TSP_TW(5), ' - 'TSP_TW_CR(6), BBCVRP(7), ' - 'NO OPTIMIZATION(100) or ' - 'LEGACY_DISTRIBUTED(101)') + raise ParamValueException( + "algorithm_type", + "Must be ALGORITHM_TYPE: " + "TSP(1), VRP(2), CVRP_TW_SD(3), " + "CVRP_TW_MD(4), TSP_TW(5), " + "TSP_TW_CR(6), BBCVRP(7), " + "NO OPTIMIZATION(100) or " + "LEGACY_DISTRIBUTED(101)", + ) def route_name(self, route_name): """ @@ -255,9 +272,9 @@ def route_name(self, route_name): :return: """ if self.check_string_type(route_name): - self._copy_data({'route_name': route_name}) + self._copy_data({"route_name": route_name}) else: - raise ParamValueException('route_name', 'Must be String') + raise ParamValueException("route_name", "Must be String") def optimization_problem_id(self, optimization_problem_id): """ @@ -266,11 +283,9 @@ def optimization_problem_id(self, optimization_problem_id): :return: """ if self.check_string_type(optimization_problem_id): - self._copy_param({'optimization_problem_id': - optimization_problem_id}) + self._copy_param({"optimization_problem_id": optimization_problem_id}) else: - raise ParamValueException('optimization_problem_id', - 'Must be String') + raise ParamValueException("optimization_problem_id", "Must be String") def optimized_callback_url(self, optimized_callback_url): """ @@ -279,10 +294,9 @@ def optimized_callback_url(self, optimized_callback_url): :return: """ if self.check_string_type(optimized_callback_url): - self._copy_param({'optimized_callback_url': optimized_callback_url}) + self._copy_param({"optimized_callback_url": optimized_callback_url}) else: - raise ParamValueException('optimized_callback_url', - 'Must be String') + raise ParamValueException("optimized_callback_url", "Must be String") def remote_ip(self, remote_ip): """ @@ -291,9 +305,9 @@ def remote_ip(self, remote_ip): :return: """ if isinstance(remote_ip, int): - self._copy_data({'remote_ip': remote_ip}) + self._copy_data({"remote_ip": remote_ip}) else: - raise ParamValueException('remote_ip', 'Must be integer') + raise ParamValueException("remote_ip", "Must be integer") def travel_mode(self, travel_mode): """ @@ -303,10 +317,11 @@ def travel_mode(self, travel_mode): :return: """ if travel_mode in TRAVEL_MODE.reverse_mapping.keys(): - self._copy_data({'travel_mode': travel_mode}) + self._copy_data({"travel_mode": travel_mode}) else: - raise ParamValueException('travel_mode', 'Must be DRIVING, ' - 'WALKING, TRUCKING') + raise ParamValueException( + "travel_mode", "Must be DRIVING, " "WALKING, TRUCKING" + ) def optimize(self, optimize): """ @@ -315,10 +330,11 @@ def optimize(self, optimize): :return: """ if optimize in OPTIMIZE.reverse_mapping.keys(): - self._copy_data({'optimize': optimize}) + self._copy_data({"optimize": optimize}) else: - raise ParamValueException('optimize', 'Must be DISTANCE, TIME, ' - 'TIME_WITH_TRAFFIC') + raise ParamValueException( + "optimize", "Must be DISTANCE, TIME, " "TIME_WITH_TRAFFIC" + ) def distance_unit(self, distance_unit): """ @@ -327,11 +343,11 @@ def distance_unit(self, distance_unit): :return: """ if distance_unit in DISTANCE_UNIT.reverse_mapping.keys(): - self._copy_data({'distance_unit': distance_unit}) + self._copy_data({"distance_unit": distance_unit}) else: - raise ParamValueException('distance_unit', 'Must be MI or KM') + raise ParamValueException("distance_unit", "Must be MI or KM") - def device_type(self, device_type, target='data'): + def device_type(self, device_type, target="data"): """ Set device_type. Options are: WEB, IPHONE, IPAD, ANDROID_PHONE, ANDROID_TABLET @@ -340,11 +356,12 @@ def device_type(self, device_type, target='data'): :return: """ if device_type in DEVICE_TYPE.reverse_mapping.keys(): - getattr(self, '_copy_%s' % target)({'device_type': device_type}) + getattr(self, "_copy_%s" % target)({"device_type": device_type}) else: - raise ParamValueException('device_type', 'Must be WEB, IPHONE, ' - 'IPAD, ANDROID_PHONE, ' - 'ANDROID_TABLET') + raise ParamValueException( + "device_type", + "Must be WEB, IPHONE, " "IPAD, ANDROID_PHONE, " "ANDROID_TABLET", + ) def route_path_output(self, route_path_output): """ @@ -355,10 +372,9 @@ def route_path_output(self, route_path_output): :return: """ if route_path_output in ROUTE_PATH_OUTPUT.reverse_mapping.keys(): - self._copy_param({'route_path_output': route_path_output}) + self._copy_param({"route_path_output": route_path_output}) else: - raise ParamValueException('route_path_output', 'Must be NONE or ' - 'POINTS') + raise ParamValueException("route_path_output", "Must be NONE or " "POINTS") def route_time(self, route_time): """ @@ -367,9 +383,9 @@ def route_time(self, route_time): :return: """ if isinstance(route_time, int): - self._copy_data({'route_time': route_time}) + self._copy_data({"route_time": route_time}) else: - raise ParamValueException('route_time', 'Must be integer') + raise ParamValueException("route_time", "Must be integer") def trailer_weight_t(self, trailer_weight_t): """ @@ -378,10 +394,9 @@ def trailer_weight_t(self, trailer_weight_t): :return: """ if isinstance(trailer_weight_t, int): - self._copy_data({'trailer_weight_t': trailer_weight_t}) + self._copy_data({"trailer_weight_t": trailer_weight_t}) else: - raise ParamValueException('trailer_weight_t', - 'Must be integer') + raise ParamValueException("trailer_weight_t", "Must be integer") def limited_weight_t(self, limited_weight_t): """ @@ -389,12 +404,10 @@ def limited_weight_t(self, limited_weight_t): :param limited_weight_t: :return: """ - if isinstance(limited_weight_t, float) or \ - isinstance(limited_weight_t, int): - self._copy_data({'limited_weight_t': limited_weight_t}) + if isinstance(limited_weight_t, float) or isinstance(limited_weight_t, int): + self._copy_data({"limited_weight_t": limited_weight_t}) else: - raise ParamValueException('limited_weight_t', - 'Must be integer') + raise ParamValueException("limited_weight_t", "Must be integer") def weight_per_axle_t(self, weight_per_axle_t): """ @@ -402,12 +415,10 @@ def weight_per_axle_t(self, weight_per_axle_t): :param weight_per_axle_t: :return: """ - if isinstance(weight_per_axle_t, float) or \ - isinstance(weight_per_axle_t, int): - self._copy_data({'weight_per_axle_t': weight_per_axle_t}) + if isinstance(weight_per_axle_t, float) or isinstance(weight_per_axle_t, int): + self._copy_data({"weight_per_axle_t": weight_per_axle_t}) else: - raise ParamValueException('weight_per_axle_t', - 'Must be integer') + raise ParamValueException("weight_per_axle_t", "Must be integer") def truck_height(self, truck_height): """ @@ -415,11 +426,10 @@ def truck_height(self, truck_height): :param truck_height: :return: """ - if isinstance(truck_height, float) or \ - isinstance(truck_height, int): - self._copy_data({'truck_height': truck_height}) + if isinstance(truck_height, float) or isinstance(truck_height, int): + self._copy_data({"truck_height": truck_height}) else: - raise ParamValueException('truck_height', 'Must be integer') + raise ParamValueException("truck_height", "Must be integer") def truck_width(self, truck_width): """ @@ -427,11 +437,10 @@ def truck_width(self, truck_width): :param truck_width: :return: """ - if isinstance(truck_width, float) or \ - isinstance(truck_width, int): - self._copy_data({'truck_width': truck_width}) + if isinstance(truck_width, float) or isinstance(truck_width, int): + self._copy_data({"truck_width": truck_width}) else: - raise ParamValueException('truck_width', 'Must be integer') + raise ParamValueException("truck_width", "Must be integer") def truck_length(self, truck_length): """ @@ -439,11 +448,10 @@ def truck_length(self, truck_length): :param truck_length: :return: """ - if isinstance(truck_length, float) or \ - isinstance(truck_length, int): - self._copy_data({'truck_length': truck_length}) + if isinstance(truck_length, float) or isinstance(truck_length, int): + self._copy_data({"truck_length": truck_length}) else: - raise ParamValueException('truck_length', 'Must be integer') + raise ParamValueException("truck_length", "Must be integer") def min_tour_size(self, min_tour_size): """ @@ -452,10 +460,9 @@ def min_tour_size(self, min_tour_size): :return: """ if isinstance(min_tour_size, int): - self._copy_data({'min_tour_size': min_tour_size}) + self._copy_data({"min_tour_size": min_tour_size}) else: - raise ParamValueException('min_tour_size', - 'Must be integer') + raise ParamValueException("min_tour_size", "Must be integer") def route_date(self, route_date): """ @@ -464,9 +471,9 @@ def route_date(self, route_date): :return: """ if isinstance(route_date, int): - self._copy_data({'route_date': route_date}) + self._copy_data({"route_date": route_date}) else: - raise ParamValueException('route_date', 'Must be integer') + raise ParamValueException("route_date", "Must be integer") def route_max_duration(self, route_max_duration): """ @@ -475,10 +482,9 @@ def route_max_duration(self, route_max_duration): :return: """ if isinstance(route_max_duration, int): - self._copy_data({'route_max_duration': route_max_duration}) + self._copy_data({"route_max_duration": route_max_duration}) else: - raise ParamValueException('route_max_duration', - 'Must be integer') + raise ParamValueException("route_max_duration", "Must be integer") def vehicle_capacity(self, vehicle_capacity): """ @@ -487,10 +493,9 @@ def vehicle_capacity(self, vehicle_capacity): :return: """ if isinstance(vehicle_capacity, int): - self._copy_data({'vehicle_capacity': vehicle_capacity}) + self._copy_data({"vehicle_capacity": vehicle_capacity}) else: - raise ParamValueException('vehicle_capacity', - 'Must be integer') + raise ParamValueException("vehicle_capacity", "Must be integer") def parts(self, parts): """ @@ -499,10 +504,9 @@ def parts(self, parts): :return: """ if isinstance(parts, int): - self._copy_data({'parts': parts}) + self._copy_data({"parts": parts}) else: - raise ParamValueException('parts', - 'Must be integer') + raise ParamValueException("parts", "Must be integer") def limit(self, limit): """ @@ -511,10 +515,9 @@ def limit(self, limit): :return: """ if isinstance(limit, int): - self._copy_param({'limit': limit}) + self._copy_param({"limit": limit}) else: - raise ParamValueException('limit', - 'Must be integer') + raise ParamValueException("limit", "Must be integer") def offset(self, offset): """ @@ -523,10 +526,9 @@ def offset(self, offset): :return: """ if isinstance(offset, int): - self._copy_param({'offset': offset}) + self._copy_param({"offset": offset}) else: - raise ParamValueException('offset', - 'Must be integer') + raise ParamValueException("offset", "Must be integer") def vehicle_max_distance_mi(self, vehicle_max_distance_mi): """ @@ -535,11 +537,9 @@ def vehicle_max_distance_mi(self, vehicle_max_distance_mi): :return: """ if isinstance(vehicle_max_distance_mi, int): - self._copy_data({'vehicle_max_distance_mi': - vehicle_max_distance_mi}) + self._copy_data({"vehicle_max_distance_mi": vehicle_max_distance_mi}) else: - raise ParamValueException('vehicle_max_distance_mi', - 'Must be integer') + raise ParamValueException("vehicle_max_distance_mi", "Must be integer") def max_tour_size(self, max_tour_size): """ @@ -548,11 +548,9 @@ def max_tour_size(self, max_tour_size): :return: """ if isinstance(max_tour_size, int): - self._copy_data({'max_tour_size': - max_tour_size}) + self._copy_data({"max_tour_size": max_tour_size}) else: - raise ParamValueException('max_tour_size', - 'Must be integer') + raise ParamValueException("max_tour_size", "Must be integer") def route_email(self, route_email): """ @@ -561,9 +559,9 @@ def route_email(self, route_email): :return: """ if self.check_string_type(route_email): - self._copy_data({'route_email': route_email}) + self._copy_data({"route_email": route_email}) else: - raise ParamValueException('route_email', 'Must be String') + raise ParamValueException("route_email", "Must be String") def metric(self, metric): """ @@ -577,15 +575,17 @@ def metric(self, metric): :return: """ if 1 <= metric <= 7: - self._copy_data({'metric': metric}) + self._copy_data({"metric": metric}) else: - raise ParamValueException('metric', - 'Must be METRIC: ' - 'ROUTE4ME_METRIC_EUCLIDEAN , ' - 'ROUTE4ME_METRIC_MANHATTAN, ' - 'ROUTE4ME_METRIC_GEODESIC' - 'ROUTE4ME_METRIC_MATRIX' - 'ROUTE4ME_METRIC_EXACT_2D') + raise ParamValueException( + "metric", + "Must be METRIC: " + "ROUTE4ME_METRIC_EUCLIDEAN , " + "ROUTE4ME_METRIC_MANHATTAN, " + "ROUTE4ME_METRIC_GEODESIC" + "ROUTE4ME_METRIC_MATRIX" + "ROUTE4ME_METRIC_EXACT_2D", + ) def store_route(self, store_route): """ @@ -594,9 +594,9 @@ def store_route(self, store_route): :return: """ if 0 <= store_route <= 1: - self._copy_data({'store_route': store_route}) + self._copy_data({"store_route": store_route}) else: - raise ParamValueException('store_route', 'Must be 0 or 1') + raise ParamValueException("store_route", "Must be 0 or 1") def lock_last(self, lock_last): """ @@ -605,9 +605,9 @@ def lock_last(self, lock_last): :return: """ if 0 <= lock_last <= 1: - self._copy_data({'lock_last': lock_last}) + self._copy_data({"lock_last": lock_last}) else: - raise ParamValueException('lock_last', 'Must be 0 or 1') + raise ParamValueException("lock_last", "Must be 0 or 1") def disable_optimization(self, disable_optimization): """ @@ -616,9 +616,9 @@ def disable_optimization(self, disable_optimization): :return: """ if 0 <= disable_optimization <= 1: - self._copy_data({'disable_optimization': disable_optimization}) + self._copy_data({"disable_optimization": disable_optimization}) else: - raise ParamValueException('disable_optimization', 'Must be 0 or 1') + raise ParamValueException("disable_optimization", "Must be 0 or 1") def shared_publicly(self, shared_publicly): """ @@ -627,9 +627,9 @@ def shared_publicly(self, shared_publicly): :return: """ if 0 <= shared_publicly <= 1: - self._copy_data({'shared_publicly': shared_publicly}) + self._copy_data({"shared_publicly": shared_publicly}) else: - raise ParamValueException('shared_publicly', 'Must be 0 or 1') + raise ParamValueException("shared_publicly", "Must be 0 or 1") def reoptimize(self, reoptimize): """ @@ -638,9 +638,9 @@ def reoptimize(self, reoptimize): :return: """ if 0 <= reoptimize <= 1: - self._copy_param({'reoptimize': reoptimize}) + self._copy_param({"reoptimize": reoptimize}) else: - raise ParamValueException('reoptimize', 'Must be 0 or 1') + raise ParamValueException("reoptimize", "Must be 0 or 1") def share_route(self, share_route): """ @@ -649,9 +649,9 @@ def share_route(self, share_route): :return: """ if 0 <= share_route <= 1: - self._copy_data({'share_route': share_route}) + self._copy_data({"share_route": share_route}) else: - raise ParamValueException('share_route', 'Must be 0 or 1') + raise ParamValueException("share_route", "Must be 0 or 1") def rt(self, rt): """ @@ -660,9 +660,9 @@ def rt(self, rt): :return: """ if 0 <= rt <= 1: - self._copy_data({'rt': rt}) + self._copy_data({"rt": rt}) else: - raise ParamValueException('rt', 'Must be 0 or 1') + raise ParamValueException("rt", "Must be 0 or 1") def has_trailer(self, has_trailer): """ @@ -671,9 +671,9 @@ def has_trailer(self, has_trailer): :return: """ if 0 <= has_trailer <= 1: - self._copy_data({'has_trailer': has_trailer}) + self._copy_data({"has_trailer": has_trailer}) else: - raise ParamValueException('has_trailer', 'Must be 0 or 1') + raise ParamValueException("has_trailer", "Must be 0 or 1") def optimization_quality(self, optimization_quality): """ @@ -682,10 +682,9 @@ def optimization_quality(self, optimization_quality): :return: """ if 1 <= optimization_quality <= 3: - self._copy_data({'optimization_quality': optimization_quality}) + self._copy_data({"optimization_quality": optimization_quality}) else: - raise ParamValueException('optimization_quality', - 'Must be between 1 to 3') + raise ParamValueException("optimization_quality", "Must be between 1 to 3") def directions(self, directions): """ @@ -694,9 +693,9 @@ def directions(self, directions): :return: """ if 0 <= directions <= 1: - self._copy_param({'directions': directions}) + self._copy_param({"directions": directions}) else: - raise ParamValueException('directions', 'Must be 0 or 1') + raise ParamValueException("directions", "Must be 0 or 1") def device_tracking_history(self, device_tracking_history): """ @@ -705,12 +704,9 @@ def device_tracking_history(self, device_tracking_history): :return: """ if 0 <= device_tracking_history <= 1: - self._copy_param({ - 'device_tracking_history': device_tracking_history - }) + self._copy_param({"device_tracking_history": device_tracking_history}) else: - raise ParamValueException('device_tracking_history', - 'Must be 0 or 1') + raise ParamValueException("device_tracking_history", "Must be 0 or 1") def uturn(self, uturn_type): """ @@ -721,12 +717,12 @@ def uturn(self, uturn_type): :return: """ if 1 <= uturn_type <= 2: - self._copy_data({'uturn': uturn_type}) + self._copy_data({"uturn": uturn_type}) else: - raise ParamValueException('uturn', - 'Must be : ' - 'UTURN_DEPART_SHORTEST or ' - 'UTURN_DEPART_TO_RIGHT') + raise ParamValueException( + "uturn", + "Must be : " "UTURN_DEPART_SHORTEST or " "UTURN_DEPART_TO_RIGHT", + ) def leftturn(self, left_turn_type): """ @@ -738,13 +734,15 @@ def leftturn(self, left_turn_type): :return: """ if 1 <= left_turn_type <= 3: - self._copy_data({'leftturn': left_turn_type}) + self._copy_data({"leftturn": left_turn_type}) else: - raise ParamValueException('leftturn', - 'Must be : ' - 'LEFTTURN_ALLOW or ' - 'LEFTTURN_FORBID or ' - 'LEFTTURN_MULTIAPPROACH') + raise ParamValueException( + "leftturn", + "Must be : " + "LEFTTURN_ALLOW or " + "LEFTTURN_FORBID or " + "LEFTTURN_MULTIAPPROACH", + ) def dm(self, dm): """ @@ -756,14 +754,14 @@ def dm(self, dm): :return: """ if dm in [1, 3, 6]: - self._copy_data({'dm': dm}) + self._copy_data({"dm": dm}) else: raise ParamValueException( - 'dm', - 'Must be : ' - 'R4M_PROPRIETARY_ROUTING or ' - 'R4M_TRAFFIC_ENGINE or ' - 'TRUCKING' + "dm", + "Must be : " + "R4M_PROPRIETARY_ROUTING or " + "R4M_TRAFFIC_ENGINE or " + "TRUCKING", ) def dirm(self, dirm): @@ -775,28 +773,26 @@ def dirm(self, dirm): :return: """ if dirm in [1, 3]: - self._copy_data({'dirm': dirm}) + self._copy_data({"dirm": dirm}) else: raise ParamValueException( - 'dirm', - 'Must be : ' - 'R4M_PROPRIETARY_INTERNAL_NAVIGATION_SYSTEM or' - 'TRUCKING' + "dirm", + "Must be : " "R4M_PROPRIETARY_INTERNAL_NAVIGATION_SYSTEM or" "TRUCKING", ) def advanced_constraints(self, advanced_constraints): if not isinstance(advanced_constraints, list): raise ParamValueException( - 'advanced_constraints', - 'Must be: List of Advanced Constraints', + "advanced_constraints", + "Must be: List of Advanced Constraints", ) self._copy_data({"advanced_constraints": advanced_constraints}) def bundling(self, bundling): if not isinstance(bundling, dict): raise ParamValueException( - 'bundling', - 'Must be: Bundling Object', + "bundling", + "Must be: Bundling Object", ) self._copy_data({"bundling": bundling}) @@ -806,7 +802,7 @@ def _copy_data(self, params): :param params: :return: """ - self.data['parameters'].update(params) + self.data["parameters"].update(params) def _copy_param(self, params): """ @@ -852,7 +848,7 @@ def validate_params(self, **kwargs): except ParamValueException as e: raise e except AttributeError: - raise ParamValueException(k, 'Not supported') + raise ParamValueException(k, "Not supported") return True def add(self, params={}, data={}): @@ -874,9 +870,9 @@ def truck_hazardous_goods(self, hazardous_goods): :return: """ if hazardous_goods in TRUCK_HAZARDOUS_GOODS.reverse_mapping.keys(): - self._copy_data({'truck_hazardous_goods': hazardous_goods}) + self._copy_data({"truck_hazardous_goods": hazardous_goods}) else: - raise ParamValueException('truck_hazardous_goods', - 'Must be MI or KM') + raise ParamValueException("truck_hazardous_goods", "Must be MI or KM") + # codebeat:enable[TOTAL_LOC, TOO_MANY_FUNCTIONS, TOTAL_COMPLEXITY] diff --git a/route4me/constants.py b/route4me/constants.py index d1bac8b..71374c8 100644 --- a/route4me/constants.py +++ b/route4me/constants.py @@ -10,8 +10,8 @@ def enum(**enums): :return: """ reverse = dict((value, key) for key, value in iteritems(enums)) - enums['reverse_mapping'] = reverse - return type('Enum', (), enums) + enums["reverse_mapping"] = reverse + return type("Enum", (), enums) def auto_enum(*sequential, **named): @@ -25,103 +25,125 @@ def auto_enum(*sequential, **named): return enum(**enums) -TYPE_OF_MATRIX = enum(R4M_PROPRIETARY_ROUTING=1, - R4M_TRAFFIC_ENGINE=3, - TRUCKING=6) - -DIRECTIONS_METHOD = enum(R4M_PROPRIETARY_INTERNAL_NAVIGATION_SYSTEM=1, - TRUCKING=3) - -ALGORITHM_TYPE = enum(TSP=1, - VRP=2, - CVRP_TW_SD=3, - CVRP_TW_MD=4, - TSP_TW=5, - TSP_TW_CR=6, - BBCVRP=7, - ADVANCED_CVRP_TW=9, - ALG_LEGACY_DISTRIBUTED=101, - ALG_NONE=100) - -TRAVEL_MODE = enum(DRIVING='Driving', - WALKING='Walking', - TRUCKING='Trucking') - -DISTANCE_UNIT = enum(MI='mi', - KM='km') - -AVOID = enum(HIGHWAYS='Highways', - TOLLS='Tolls', - MINIMIZE_HIGHWAYS='minimizeHighways', - MINIMIZE_TOLLS='minimizeTolls', - NONE='') - -OPTIMIZE = enum(DISTANCE='Distance', - TIME='Time', - TIME_WITH_TRAFFIC='timeWithTraffic') - -METRIC = auto_enum('ROUTE4ME_METRIC_EUCLIDEAN', - 'ROUTE4ME_METRIC_MANHATTAN', - 'ROUTE4ME_METRIC_GEODESIC', - 'ROUTE4ME_METRIC_MATRIX', - 'ROUTE4ME_METRIC_EXACT_2D', ) - -DEVICE_TYPE = enum(WEB="web", - IPHONE="iphone", - IPAD="ipad", - ANDROID_PHONE="android_phone", - ANDROID_TABLET="android_tablet") - -FORMAT = enum(CSV='csv', - SERIALIZED='serialized', - XML='xml', - JSON='json') - -OPTIMIZATION_STATE = auto_enum('OPTIMIZATION_STATE_INITIAL', - 'OPTIMIZATION_STATE_MATRIX_PROCESSING', - 'OPTIMIZATION_STATE_OPTIMIZING', - 'OPTIMIZATION_STATE_OPTIMIZED', - 'OPTIMIZATION_STATE_ERROR', - 'OPTIMIZATION_STATE_COMPUTING_DIRECTIONS', ) - -ROUTE_PATH_OUTPUT = enum(NONE='None', - POINTS='Points') - -UTURN = auto_enum('UTURN_DEPART_SHORTEST', - 'UTURN_DEPART_TO_RIGHT') - -LEFT_TURN = auto_enum('LEFTTURN_ALLOW', - 'LEFTTURN_FORBID', - 'LEFTTURN_MULTIAPPROACH') - -TRUCK_HAZARDOUS_GOODS = enum(NONE='', - EXPLOSIVE='explosive', - GAS='gas', - FLAMMABLE='flammable', - COMBUSTIBLE='combustible', - ORGANIC='organic', - POISON='poison', - RADIOACTIVE='radioActive', - CORROSIVE='corrosive', - POISONOUSINHALATION='poisonousInhalation', - HARMFULTOWATER='harmfulToWater', - OTHER='other', - ALLHAZARDOUSGOODS='allHazardousGoods') - -TERRITORY_TYPE = enum(CIRCLE='circle', - POLY='poly', - RECT='rect') - -ADDRESS_STOP_TYPE = enum(DELIVERY="DELIVERY", - PICKUP="PICKUP", - BREAK="BREAK", - MEETUP="MEETUP", - SERVICE="SERVICE", - VISIT="VISIT", - DRIVEBY="DRIVEBY") - -ROUTE_STATUS = enum(PLANNED="planned", - STARTED="started", - PAUSED="paused", - RESUMED="resumed", - COMPLETED="completed") +TYPE_OF_MATRIX = enum(R4M_PROPRIETARY_ROUTING=1, R4M_TRAFFIC_ENGINE=3, TRUCKING=6) + +DIRECTIONS_METHOD = enum(R4M_PROPRIETARY_INTERNAL_NAVIGATION_SYSTEM=1, TRUCKING=3) + +ALGORITHM_TYPE = enum( + TSP=1, + VRP=2, + CVRP_TW_SD=3, + CVRP_TW_MD=4, + TSP_TW=5, + TSP_TW_CR=6, + BBCVRP=7, + ADVANCED_CVRP_TW=9, + ALG_LEGACY_DISTRIBUTED=101, + ALG_NONE=100, +) + +TRAVEL_MODE = enum(DRIVING="Driving", WALKING="Walking", TRUCKING="Trucking") + +DISTANCE_UNIT = enum(MI="mi", KM="km") + +AVOID = enum( + HIGHWAYS="Highways", + TOLLS="Tolls", + MINIMIZE_HIGHWAYS="minimizeHighways", + MINIMIZE_TOLLS="minimizeTolls", + NONE="", +) + +OPTIMIZE = enum(DISTANCE="Distance", TIME="Time", TIME_WITH_TRAFFIC="timeWithTraffic") + +METRIC = auto_enum( + "ROUTE4ME_METRIC_EUCLIDEAN", + "ROUTE4ME_METRIC_MANHATTAN", + "ROUTE4ME_METRIC_GEODESIC", + "ROUTE4ME_METRIC_MATRIX", + "ROUTE4ME_METRIC_EXACT_2D", +) + +DEVICE_TYPE = enum( + WEB="web", + IPHONE="iphone", + IPAD="ipad", + ANDROID_PHONE="android_phone", + ANDROID_TABLET="android_tablet", +) + +FORMAT = enum(CSV="csv", SERIALIZED="serialized", XML="xml", JSON="json") + +OPTIMIZATION_STATE = auto_enum( + "OPTIMIZATION_STATE_INITIAL", + "OPTIMIZATION_STATE_MATRIX_PROCESSING", + "OPTIMIZATION_STATE_OPTIMIZING", + "OPTIMIZATION_STATE_OPTIMIZED", + "OPTIMIZATION_STATE_ERROR", + "OPTIMIZATION_STATE_COMPUTING_DIRECTIONS", +) + +ROUTE_PATH_OUTPUT = enum(NONE="None", POINTS="Points") + +UTURN = auto_enum("UTURN_DEPART_SHORTEST", "UTURN_DEPART_TO_RIGHT") + +LEFT_TURN = auto_enum("LEFTTURN_ALLOW", "LEFTTURN_FORBID", "LEFTTURN_MULTIAPPROACH") + +TRUCK_HAZARDOUS_GOODS = enum( + NONE="", + EXPLOSIVE="explosive", + GAS="gas", + FLAMMABLE="flammable", + COMBUSTIBLE="combustible", + ORGANIC="organic", + POISON="poison", + RADIOACTIVE="radioActive", + CORROSIVE="corrosive", + POISONOUSINHALATION="poisonousInhalation", + HARMFULTOWATER="harmfulToWater", + OTHER="other", + ALLHAZARDOUSGOODS="allHazardousGoods", +) + +TERRITORY_TYPE = enum(CIRCLE="circle", POLY="poly", RECT="rect") + +ADDRESS_STOP_TYPE = enum( + DELIVERY="DELIVERY", + PICKUP="PICKUP", + BREAK="BREAK", + MEETUP="MEETUP", + SERVICE="SERVICE", + VISIT="VISIT", + DRIVEBY="DRIVEBY", +) + +ROUTE_STATUS = enum( + PLANNED="planned", + STARTED="started", + PAUSED="paused", + RESUMED="resumed", + COMPLETED="completed", +) + + +ORDER_STATUS = enum( + NEW=0, + POSSESSION_SCAN=1, + SORTED=2, + LOADED=3, + MISSING=4, + DAMAGED=5, + MANUALLY_LOADED=6, + ROUTED=7, + UNROUTED=8, + SORTED_BY_ROUTE=9, + ROUTE_STARTED=10, + FAILED=11, + SKIPPED=12, + DONE=13, + CANCELED=14, + SCHEDULED=15, + PICK_UP_SCAN=16, + UNABLE_TO_DELIVER=17, + HOLD_FOR_PICKUP=18, +) diff --git a/route4me/exceptions.py b/route4me/exceptions.py index 02a947e..b5b2c1c 100644 --- a/route4me/exceptions.py +++ b/route4me/exceptions.py @@ -1,8 +1,9 @@ # -*- coding: utf-8 -*- import json +from .version import VERSION_STRING -DRIVER_VERSION = 'route4me-python-driver-0.0.1' +DRIVER_VERSION = f"route4me-python-sdk-{VERSION_STRING}" class APIException(Exception): @@ -14,10 +15,12 @@ def __init__(self, status_code, response, url): self.status_code = status_code self.response = response self.url = url - exception = {'http_status_code': status_code, - 'response': response, - 'url': url, - 'driver_version': DRIVER_VERSION} + exception = { + "http_status_code": status_code, + "response": response, + "url": url, + "driver_version": DRIVER_VERSION, + } Exception.__init__(self, exception) def get_status_code(self): @@ -40,9 +43,9 @@ def to_dict(self): """ try: response_dict = json.loads(self.response) - return {'errors': response_dict.get('errors')} + return {"errors": response_dict.get("errors")} except json.JSONDecodeError: - return {'errors': 'Unexpected server response'} + return {"errors": "Unexpected server response"} class ParamValueException(Exception): @@ -53,8 +56,10 @@ class ParamValueException(Exception): def __init__(self, param, msg): self.param = param self.msg = msg - exception = {'param': param, - 'msg': msg, } + exception = { + "param": param, + "msg": msg, + } Exception.__init__(self, exception) def get_msg(self): diff --git a/route4me/optimization.py b/route4me/optimization.py index 3731dfa..008331f 100644 --- a/route4me/optimization.py +++ b/route4me/optimization.py @@ -4,15 +4,16 @@ from .base import Base from .exceptions import ParamValueException, APIException from .slowdowns import Slowdowns +from .utils import is_valid_datetime class Optimization(Base): """ - An Optimization Problem is a collection of addresses that need to be - visited. This is distinct from a Route, which is a sequence of - addresses that need to be visited by a single vehicle and a single - driver in a fixed time period. Solving an Optimization Problem - results in a number of routes. + An Optimization Problem is a collection of addresses that need to be + visited. This is distinct from a Route, which is a sequence of + addresses that need to be visited by a single vehicle and a single + driver in a fixed time period. Solving an Optimization Problem + results in a number of routes. """ def __init__(self, api): @@ -30,17 +31,27 @@ def get_optimizations(self, **kwargs): :raise: ParamValueException if required params are not present. """ - kwargs.update({'api_key': self.params['api_key'], }) - if self.check_required_params(kwargs, ['limit', 'offset', ]): + kwargs.update( + { + "api_key": self.params["api_key"], + } + ) + if self.check_required_params( + kwargs, + [ + "limit", + "offset", + ], + ): try: - response = self.api._make_request(API_HOST, - kwargs, - self.api._request_get) + response = self.api._make_request( + API_HOST, kwargs, self.api._request_get + ) return response.json() except APIException as e: return e.to_dict() else: - raise ParamValueException('params', 'Params are not complete') + raise ParamValueException("params", "Params are not complete") def get_optimization(self, **kwargs): """ @@ -49,17 +60,26 @@ def get_optimization(self, **kwargs): :raise: ParamValueException if required params are not present. """ - kwargs.update({'api_key': self.params['api_key'], }) - if self.check_required_params(kwargs, ['optimization_problem_id', ]): + kwargs.update( + { + "api_key": self.params["api_key"], + } + ) + if self.check_required_params( + kwargs, + [ + "optimization_problem_id", + ], + ): try: - response = self.api._make_request(API_HOST, - kwargs, - self.api._request_get) + response = self.api._make_request( + API_HOST, kwargs, self.api._request_get + ) return response.json() except APIException as e: return e.to_dict() else: - raise ParamValueException('params', 'Params are not complete') + raise ParamValueException("params", "Params are not complete") def update_optimization(self, **kwargs): """ @@ -68,20 +88,23 @@ def update_optimization(self, **kwargs): :raise: ParamValueException if required params are not present. """ - kwargs.update({'api_key': self.params['api_key'], }) - if self.check_required_params(kwargs, ['optimization_problem_id', - 'addresses', - 'reoptimize']): + kwargs.update( + { + "api_key": self.params["api_key"], + } + ) + if self.check_required_params( + kwargs, ["optimization_problem_id", "addresses", "reoptimize"] + ): try: - response = self.api._make_request(API_HOST, - self.params, - self.api._request_put, - data=kwargs) + response = self.api._make_request( + API_HOST, self.params, self.api._request_put, data=kwargs + ) return response.json() except APIException as e: return e.to_dict() else: - raise ParamValueException('params', 'Params are not complete') + raise ParamValueException("params", "Params are not complete") def delete_optimization(self, **kwargs): """ @@ -91,17 +114,21 @@ def delete_optimization(self, **kwargs): """ self.json_data = kwargs - if self.check_required_params(kwargs, ['optimization_problem_ids', ]): + if self.check_required_params( + kwargs, + [ + "optimization_problem_ids", + ], + ): try: - response = self.api._make_request(API_HOST, - self.params, - self.api._request_delete, - json=kwargs) + response = self.api._make_request( + API_HOST, self.params, self.api._request_delete, json=kwargs + ) return response.json() except APIException as e: return e.to_dict() else: - raise ParamValueException('params', 'Params are not complete') + raise ParamValueException("params", "Params are not complete") def delete_address_from_optimization(self, **kwargs): """ @@ -110,18 +137,23 @@ def delete_address_from_optimization(self, **kwargs): :raise: ParamValueException if required params are not present. """ - kwargs.update({'api_key': self.params['api_key'], }) - if self.check_required_params(kwargs, ['optimization_problem_id', - 'route_destination_id']): + kwargs.update( + { + "api_key": self.params["api_key"], + } + ) + if self.check_required_params( + kwargs, ["optimization_problem_id", "route_destination_id"] + ): try: - response = self.api._make_request(ADDRESS_HOST, - kwargs, - self.api._request_delete) + response = self.api._make_request( + ADDRESS_HOST, kwargs, self.api._request_delete + ) return response.json() except APIException as e: return e.to_dict() else: - raise ParamValueException('params', 'Params are not complete') + raise ParamValueException("params", "Params are not complete") def set_slowdowns(self, service_time, travel_time): """ @@ -132,3 +164,33 @@ def set_slowdowns(self, service_time, travel_time): """ slowdowns = Slowdowns(service_time, travel_time) self._copy_data({"slowdowns": slowdowns.to_dict()}) + + def set_order_aggregation_groups( + self, + scheduled_for_range, + aggregations_id, + order_status, + aggregation_profile_overrides, + split=True, + ): + """ + Set Orders Aggregations groups + """ + order_aggregation_groups = { + "split": split, + "aggregations_id": aggregations_id, + "filters": { + "scheduled_for_range": scheduled_for_range, + "order_status": order_status, + }, + "aggregation_profile_overrides": aggregation_profile_overrides, + } + self.data.update({"order_aggregation_groups": order_aggregation_groups}) + + def route_start_date_local(self, route_start_date_local): + if not is_valid_datetime(route_start_date_local, "%Y-%m-%d"): + ParamValueException( + "params", + "Route Start Date Local is invalid, must be YYYY-MM-DD date format", + ) + self._copy_data({"route_start_date_local": route_start_date_local}) diff --git a/route4me/optimization_profiles.py b/route4me/optimization_profiles.py new file mode 100644 index 0000000..af222fe --- /dev/null +++ b/route4me/optimization_profiles.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- +from .base import Base +from .exceptions import APIException + +from .api_endpoints import OPTIMIZATION_PROFILES_V5 + + +class OptimizationProfiles(Base): + + def __init__(self, api): + """ + Optimization Profile Instance + :param api: + :return: + """ + Base.__init__(self, api) + + def get_optimization_profiles(self): + try: + response = self.api._make_request( + OPTIMIZATION_PROFILES_V5, self.params, self.api._request_get + ) + data = response.json() + return data + except APIException as e: + print(response.content) + return e.to_dict() + except ValueError: + return response.content diff --git a/route4me/orders_group.py b/route4me/orders_group.py new file mode 100644 index 0000000..183d341 --- /dev/null +++ b/route4me/orders_group.py @@ -0,0 +1,62 @@ +# -*- coding: utf-8 -*- +from .base import Base +from .exceptions import ParamValueException, APIException +from .utils import is_valid_datetime + +from .api_endpoints import ORDERS_GROUP_V5, ASSIGN_PROFILES_V5 + + +class OrdersGroup(Base): + + def __init__(self, api): + """ + Order Groups Instance + :param api: + :return: + """ + Base.__init__(self, api) + + def _check_scheduled_for_range(self, scheduled_for_range): + for s in scheduled_for_range: + if not is_valid_datetime(s, "%Y-%m-%d"): + return False + return True + + def get_orders_group(self, scheduled_for_range, statuses): + if not self._check_scheduled_for_range(scheduled_for_range): + raise ParamValueException("Invalid Scheduled Range") + data = { + "filters": { + "scheduled_for_range": scheduled_for_range, + "statuses": statuses, + } + } + items = [] + try: + response = self.api._make_request( + ORDERS_GROUP_V5, self.params, self.api._request_post, json=data + ) + data = response.json() + if "data" in data.keys(): + items = data["data"].get("items", []) + return items + except APIException as e: + return e.to_dict() + except ValueError: + return response.content + + def assign_optimization_profile(self, optimization_profile_id, group_id): + data = { + "group_id": group_id, + "optimization_profile_id": optimization_profile_id, + } + try: + response = self.api._make_request( + ASSIGN_PROFILES_V5, self.params, self.api._request_post, json=data + ) + data = response.json() + return data + except APIException as e: + return e.to_dict() + except ValueError: + return response.content diff --git a/route4me/utils.py b/route4me/utils.py index c2d6d37..6204d9f 100644 --- a/route4me/utils.py +++ b/route4me/utils.py @@ -14,10 +14,10 @@ def _json_object_hook(d): keys = [] for k in d.keys(): if k[0].isdigit(): - k = 'd_{}'.format(k) + k = "d_{}".format(k) keys.append(k) - return namedtuple('X', keys)(*d.values()) + return namedtuple("X", keys)(*d.values()) def json2obj(data): @@ -56,14 +56,15 @@ def clean_dict(data): return data -def is_valid_datetime(s): +def is_valid_datetime(s, date_format="%Y-%m-%d %H:%M:%S"): """ - Checks if the given datetime string is in 'YYYY-MM-DD HH:MM:SS' format. + Checks if the given datetime string is valid. + Default format is 'YYYY-MM-DD HH:MM:SS'. :param s: The datetime string to validate. :return: True if the datetime string is valid, False otherwise. """ try: - datetime.strptime(str(s), '%Y-%m-%d %H:%M:%S') + datetime.strptime(str(s), date_format) return True except ValueError: return False diff --git a/route4me/version.py b/route4me/version.py index a7efde5..1f11112 100644 --- a/route4me/version.py +++ b/route4me/version.py @@ -6,17 +6,17 @@ # VERSION.py - MAINTAINER's. Don't edit, if you don't know what are you doing # ============================================================================== -VERSION = (0, 1, 5, 1) -RELEASE_SUFFIX = '' +VERSION = (0, 1, 6, 0) +RELEASE_SUFFIX = "" -VERSION_STRING = '.'.join([str(x) for x in VERSION]) +VERSION_STRING = ".".join([str(x) for x in VERSION]) RELEASE_STRING = "v{}{}".format(VERSION_STRING, RELEASE_SUFFIX) -PROJECT = 'Route4Me Python SDK' -COPYRIGHT = '2016-2021 © Route4Me Python Team' -AUTHOR = 'Route4Me Python Team (SDK)' -AUTHOR_EMAIL = 'juan@route4me.com' -TITLE = 'route4me' -LICENSE = 'ISC' -BUILD = None # TRAVIS_COMMIT +PROJECT = "Route4Me Python SDK" +COPYRIGHT = "2016-2021 © Route4Me Python Team" +AUTHOR = "Route4Me Python Team (SDK)" +AUTHOR_EMAIL = "juan@route4me.com" +TITLE = "route4me" +LICENSE = "ISC" +BUILD = None # TRAVIS_COMMIT COMMIT = None # TRAVIS_BUILD_NUMBER diff --git a/setup.py b/setup.py index e4a3dd4..cfe3be9 100755 --- a/setup.py +++ b/setup.py @@ -6,12 +6,11 @@ from VERSION import PROJECT -from VERSION import COPYRIGHT from VERSION import AUTHOR from VERSION import AUTHOR_EMAIL from VERSION import TITLE from VERSION import LICENSE -from VERSION import RELEASE_STRING +from VERSION import VERSION_STRING def read(file_name): @@ -22,47 +21,46 @@ def read(file_name): def rewrite_version(): - with open('VERSION.py', 'r') as inp: + with open("VERSION.py", "r") as inp: txt = inp.read() - outname = os.path.join('route4me', 'version.py') - with open(outname, 'w') as out: + outname = os.path.join("route4me", "version.py") + with open(outname, "w") as out: out.write(txt) + rewrite_version() setup( - name=TITLE, - url='https://github.com/route4me/route4me-python-sdk', - bugtrack_url='https://github.com/route4me/route4me-python-sdk/issues', - copyright=COPYRIGHT, + url="https://github.com/route4me/route4me-python-sdk", author=AUTHOR, description=PROJECT, - version=RELEASE_STRING, + version=VERSION_STRING, author_email=AUTHOR_EMAIL, license=LICENSE, keywords="rout4me, python, sdk, api", packages=find_packages( - include=['route4me', 'route4me.*'], - exclude=['*_test*'], + include=["route4me", "route4me.*"], + exclude=["*_test*"], ), zip_safe=True, - platforms='any', - long_description=read('README.md'), - long_description_content_type='text/markdown', + platforms="any", + long_description=read("README.md"), + long_description_content_type="text/markdown", classifiers=[ - 'Environment :: Other Environment', - 'License :: OSI Approved :: ISC License (ISCL)', - 'Operating System :: OS Independent', - 'Programming Language :: Python', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.8', - 'Programming Language :: Python :: Implementation', - 'Programming Language :: Python :: Implementation :: CPython', - 'Programming Language :: Python :: Implementation :: PyPy', - 'Topic :: Software Development :: Libraries :: Python Modules'], + "Environment :: Other Environment", + "License :: OSI Approved :: ISC License (ISCL)", + "Operating System :: OS Independent", + "Programming Language :: Python", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: Implementation", + "Programming Language :: Python :: Implementation :: CPython", + "Programming Language :: Python :: Implementation :: PyPy", + "Topic :: Software Development :: Libraries :: Python Modules", + ], test_suite="tests", - install_requires=['six', 'requests'], + install_requires=["six", "requests"], )