diff --git a/CHANGELOG.md b/CHANGELOG.md index c52b303..0e4245b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,14 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.9.0] - 2021-08-17 + +### Added + +- Fixed ability to create Orders with `metadata` +- Add support for querying Orders by `metadata` +- Added `transaction_value_eth_gwei` as an alternative way to compute transaction level emissions for ethereum + ## [1.8.0] - 2021-07-20 ### Added diff --git a/patch_api/__init__.py b/patch_api/__init__.py index 32918e3..fffe9b3 100644 --- a/patch_api/__init__.py +++ b/patch_api/__init__.py @@ -15,7 +15,7 @@ from __future__ import absolute_import -__version__ = "1.8.0" +__version__ = "1.9.0" # import ApiClient from patch_api.api_client import ApiClient diff --git a/patch_api/api/estimates_api.py b/patch_api/api/estimates_api.py index 95d1af0..fc5d274 100644 --- a/patch_api/api/estimates_api.py +++ b/patch_api/api/estimates_api.py @@ -148,8 +148,15 @@ def create_bitcoin_estimate_with_http_info( path_params = {} query_params = [] + + # do not add duplicate keys to query_params list + existing_keys = [] + for param in query_params: + existing_keys.append(param[0]) + for key in kwargs: - query_params.append([key, kwargs.get(key)]) + if key not in existing_keys: + query_params.append([key, kwargs.get(key)]) header_params = {} @@ -294,8 +301,15 @@ def create_ethereum_estimate_with_http_info( path_params = {} query_params = [] + + # do not add duplicate keys to query_params list + existing_keys = [] + for param in query_params: + existing_keys.append(param[0]) + for key in kwargs: - query_params.append([key, kwargs.get(key)]) + if key not in existing_keys: + query_params.append([key, kwargs.get(key)]) header_params = {} @@ -440,8 +454,15 @@ def create_flight_estimate_with_http_info( path_params = {} query_params = [] + + # do not add duplicate keys to query_params list + existing_keys = [] + for param in query_params: + existing_keys.append(param[0]) + for key in kwargs: - query_params.append([key, kwargs.get(key)]) + if key not in existing_keys: + query_params.append([key, kwargs.get(key)]) header_params = {} @@ -586,8 +607,15 @@ def create_mass_estimate_with_http_info( path_params = {} query_params = [] + + # do not add duplicate keys to query_params list + existing_keys = [] + for param in query_params: + existing_keys.append(param[0]) + for key in kwargs: - query_params.append([key, kwargs.get(key)]) + if key not in existing_keys: + query_params.append([key, kwargs.get(key)]) header_params = {} @@ -732,8 +760,15 @@ def create_shipping_estimate_with_http_info( path_params = {} query_params = [] + + # do not add duplicate keys to query_params list + existing_keys = [] + for param in query_params: + existing_keys.append(param[0]) + for key in kwargs: - query_params.append([key, kwargs.get(key)]) + if key not in existing_keys: + query_params.append([key, kwargs.get(key)]) header_params = {} @@ -878,8 +913,15 @@ def create_vehicle_estimate_with_http_info( path_params = {} query_params = [] + + # do not add duplicate keys to query_params list + existing_keys = [] + for param in query_params: + existing_keys.append(param[0]) + for key in kwargs: - query_params.append([key, kwargs.get(key)]) + if key not in existing_keys: + query_params.append([key, kwargs.get(key)]) header_params = {} @@ -1017,8 +1059,15 @@ def retrieve_estimate_with_http_info(self, id, **kwargs): # noqa: E501 path_params["id"] = local_var_params["id"] # noqa: E501 query_params = [] + + # do not add duplicate keys to query_params list + existing_keys = [] + for param in query_params: + existing_keys.append(param[0]) + for key in kwargs: - query_params.append([key, kwargs.get(key)]) + if key not in existing_keys: + query_params.append([key, kwargs.get(key)]) header_params = {} @@ -1140,11 +1189,18 @@ def retrieve_estimates_with_http_info(self, **kwargs): # noqa: E501 path_params = {} query_params = [] - for key in kwargs: - query_params.append([key, kwargs.get(key)]) if "page" in local_var_params: query_params.append(("page", local_var_params["page"])) # noqa: E501 + # do not add duplicate keys to query_params list + existing_keys = [] + for param in query_params: + existing_keys.append(param[0]) + + for key in kwargs: + if key not in existing_keys: + query_params.append([key, kwargs.get(key)]) + header_params = {} form_params = [] diff --git a/patch_api/api/orders_api.py b/patch_api/api/orders_api.py index 2bf0532..ccaf0f6 100644 --- a/patch_api/api/orders_api.py +++ b/patch_api/api/orders_api.py @@ -141,8 +141,15 @@ def cancel_order_with_http_info(self, id, **kwargs): # noqa: E501 path_params["id"] = local_var_params["id"] # noqa: E501 query_params = [] + + # do not add duplicate keys to query_params list + existing_keys = [] + for param in query_params: + existing_keys.append(param[0]) + for key in kwargs: - query_params.append([key, kwargs.get(key)]) + if key not in existing_keys: + query_params.append([key, kwargs.get(key)]) header_params = {} @@ -274,8 +281,15 @@ def create_order_with_http_info(self, create_order_request, **kwargs): # noqa: path_params = {} query_params = [] + + # do not add duplicate keys to query_params list + existing_keys = [] + for param in query_params: + existing_keys.append(param[0]) + for key in kwargs: - query_params.append([key, kwargs.get(key)]) + if key not in existing_keys: + query_params.append([key, kwargs.get(key)]) header_params = {} @@ -413,8 +427,15 @@ def place_order_with_http_info(self, id, **kwargs): # noqa: E501 path_params["id"] = local_var_params["id"] # noqa: E501 query_params = [] + + # do not add duplicate keys to query_params list + existing_keys = [] + for param in query_params: + existing_keys.append(param[0]) + for key in kwargs: - query_params.append([key, kwargs.get(key)]) + if key not in existing_keys: + query_params.append([key, kwargs.get(key)]) header_params = {} @@ -543,8 +564,15 @@ def retrieve_order_with_http_info(self, id, **kwargs): # noqa: E501 path_params["id"] = local_var_params["id"] # noqa: E501 query_params = [] + + # do not add duplicate keys to query_params list + existing_keys = [] + for param in query_params: + existing_keys.append(param[0]) + for key in kwargs: - query_params.append([key, kwargs.get(key)]) + if key not in existing_keys: + query_params.append([key, kwargs.get(key)]) header_params = {} @@ -591,6 +619,9 @@ def retrieve_orders(self, **kwargs): # noqa: E501 :param async_req bool: execute request asynchronously :param int page: + :param str metadata: + :param str metadata_example1: + :param str metadata_example2: :param _preload_content: if False, the urllib3.HTTPResponse object will be returned without reading/decoding response data. Default is True. @@ -616,6 +647,9 @@ def retrieve_orders_with_http_info(self, **kwargs): # noqa: E501 :param async_req bool: execute request asynchronously :param int page: + :param str metadata: + :param str metadata_example1: + :param str metadata_example2: :param _return_http_data_only: response data without head status code and headers :param _preload_content: if False, the urllib3.HTTPResponse object will @@ -632,7 +666,12 @@ def retrieve_orders_with_http_info(self, **kwargs): # noqa: E501 local_var_params = locals() - all_params = ["page"] # noqa: E501 + all_params = [ + "page", + "metadata", + "metadata_example1", + "metadata_example2", + ] # noqa: E501 all_params.append("async_req") all_params.append("_return_http_data_only") all_params.append("_preload_content") @@ -666,10 +705,29 @@ def retrieve_orders_with_http_info(self, **kwargs): # noqa: E501 path_params = {} query_params = [] - for key in kwargs: - query_params.append([key, kwargs.get(key)]) if "page" in local_var_params: query_params.append(("page", local_var_params["page"])) # noqa: E501 + if "metadata" in local_var_params: + query_params.append( + ("metadata", local_var_params["metadata"]) + ) # noqa: E501 + if "metadata_example1" in local_var_params: + query_params.append( + ("metadata[example1]", local_var_params["metadata_example1"]) + ) # noqa: E501 + if "metadata_example2" in local_var_params: + query_params.append( + ("metadata[example2]", local_var_params["metadata_example2"]) + ) # noqa: E501 + + # do not add duplicate keys to query_params list + existing_keys = [] + for param in query_params: + existing_keys.append(param[0]) + + for key in kwargs: + if key not in existing_keys: + query_params.append([key, kwargs.get(key)]) header_params = {} diff --git a/patch_api/api/preferences_api.py b/patch_api/api/preferences_api.py index d45e3fb..8160bdc 100644 --- a/patch_api/api/preferences_api.py +++ b/patch_api/api/preferences_api.py @@ -146,8 +146,15 @@ def create_preference_with_http_info( path_params = {} query_params = [] + + # do not add duplicate keys to query_params list + existing_keys = [] + for param in query_params: + existing_keys.append(param[0]) + for key in kwargs: - query_params.append([key, kwargs.get(key)]) + if key not in existing_keys: + query_params.append([key, kwargs.get(key)]) header_params = {} @@ -285,8 +292,15 @@ def delete_preference_with_http_info(self, id, **kwargs): # noqa: E501 path_params["id"] = local_var_params["id"] # noqa: E501 query_params = [] + + # do not add duplicate keys to query_params list + existing_keys = [] + for param in query_params: + existing_keys.append(param[0]) + for key in kwargs: - query_params.append([key, kwargs.get(key)]) + if key not in existing_keys: + query_params.append([key, kwargs.get(key)]) header_params = {} @@ -415,8 +429,15 @@ def retrieve_preference_with_http_info(self, id, **kwargs): # noqa: E501 path_params["id"] = local_var_params["id"] # noqa: E501 query_params = [] + + # do not add duplicate keys to query_params list + existing_keys = [] + for param in query_params: + existing_keys.append(param[0]) + for key in kwargs: - query_params.append([key, kwargs.get(key)]) + if key not in existing_keys: + query_params.append([key, kwargs.get(key)]) header_params = {} @@ -538,11 +559,18 @@ def retrieve_preferences_with_http_info(self, **kwargs): # noqa: E501 path_params = {} query_params = [] - for key in kwargs: - query_params.append([key, kwargs.get(key)]) if "page" in local_var_params: query_params.append(("page", local_var_params["page"])) # noqa: E501 + # do not add duplicate keys to query_params list + existing_keys = [] + for param in query_params: + existing_keys.append(param[0]) + + for key in kwargs: + if key not in existing_keys: + query_params.append([key, kwargs.get(key)]) + header_params = {} form_params = [] diff --git a/patch_api/api/projects_api.py b/patch_api/api/projects_api.py index 64fa2b4..438ac43 100644 --- a/patch_api/api/projects_api.py +++ b/patch_api/api/projects_api.py @@ -141,8 +141,15 @@ def retrieve_project_with_http_info(self, id, **kwargs): # noqa: E501 path_params["id"] = local_var_params["id"] # noqa: E501 query_params = [] + + # do not add duplicate keys to query_params list + existing_keys = [] + for param in query_params: + existing_keys.append(param[0]) + for key in kwargs: - query_params.append([key, kwargs.get(key)]) + if key not in existing_keys: + query_params.append([key, kwargs.get(key)]) header_params = {} @@ -270,8 +277,6 @@ def retrieve_projects_with_http_info(self, **kwargs): # noqa: E501 path_params = {} query_params = [] - for key in kwargs: - query_params.append([key, kwargs.get(key)]) if "page" in local_var_params: query_params.append(("page", local_var_params["page"])) # noqa: E501 if "country" in local_var_params: @@ -283,6 +288,15 @@ def retrieve_projects_with_http_info(self, **kwargs): # noqa: E501 ("minimum_available_mass", local_var_params["minimum_available_mass"]) ) # noqa: E501 + # do not add duplicate keys to query_params list + existing_keys = [] + for param in query_params: + existing_keys.append(param[0]) + + for key in kwargs: + if key not in existing_keys: + query_params.append([key, kwargs.get(key)]) + header_params = {} form_params = [] diff --git a/patch_api/api_client.py b/patch_api/api_client.py index 5beb1c3..c3e4256 100644 --- a/patch_api/api_client.py +++ b/patch_api/api_client.py @@ -91,7 +91,7 @@ def __init__( self.default_headers[header_name] = header_value self.cookie = cookie # Set default User-Agent. - self.user_agent = "OpenAPI-Generator/1.8.0/python" + self.user_agent = "OpenAPI-Generator/1.9.0/python" def __del__(self): if self._pool: diff --git a/patch_api/configuration.py b/patch_api/configuration.py index 80ed119..3ae9bee 100644 --- a/patch_api/configuration.py +++ b/patch_api/configuration.py @@ -341,7 +341,7 @@ def to_debug_report(self): "OS: {env}\n" "Python Version: {pyversion}\n" "Version of the API: v1\n" - "SDK Package Version: 1.8.0".format(env=sys.platform, pyversion=sys.version) + "SDK Package Version: 1.9.0".format(env=sys.platform, pyversion=sys.version) ) def get_host_settings(self): diff --git a/patch_api/models/estimate.py b/patch_api/models/estimate.py index e767292..3792db3 100644 --- a/patch_api/models/estimate.py +++ b/patch_api/models/estimate.py @@ -137,7 +137,7 @@ def production(self, production): def type(self): """Gets the type of this Estimate. # noqa: E501 - The type of estimate. Available types are mass, flight, shipping, and vehicle. # noqa: E501 + The type of estimate. Available types are mass, flight, shipping, vehicle, and crypto. # noqa: E501 :return: The type of this Estimate. # noqa: E501 :rtype: str @@ -148,7 +148,7 @@ def type(self): def type(self, type): """Sets the type of this Estimate. - The type of estimate. Available types are mass, flight, shipping, and vehicle. # noqa: E501 + The type of estimate. Available types are mass, flight, shipping, vehicle, and crypto. # noqa: E501 :param type: The type of this Estimate. # noqa: E501 :type: str diff --git a/patch_api/rest.py b/patch_api/rest.py index f67e65f..20090ee 100644 --- a/patch_api/rest.py +++ b/patch_api/rest.py @@ -25,6 +25,7 @@ import six from six.moves.urllib.parse import urlencode import urllib3 +import urllib.parse from patch_api.exceptions import ApiException, ApiValueError @@ -110,6 +111,60 @@ def __init__(self, api_key, configuration, pools_size=4, maxsize=None): **addition_pool_args ) + def recursive_urlencode(self, d): + """URL-encode a multidimensional dictionary. + + >>> data = {'a': 'b&c', 'd': {'e': {'f&g': 'h*i'}}, 'j': 'k'} + >>> recursive_urlencode(data) + u'a=b%26c&j=k&d[e][f%26g]=h%2Ai' + """ + + def recursion(d, base=[]): + pairs = [] + + for key, value in d.items(): + new_base = base + [key] + if hasattr(value, "values"): + pairs += recursion(value, new_base) + else: + new_pair = None + if len(new_base) > 1: + first = urllib.parse.quote(new_base.pop(0)) + rest = map(lambda x: urllib.parse.quote(x), new_base) + new_pair = "%s[%s]=%s" % ( + first, + "][".join(rest), + urllib.parse.quote(str(value)), + ) + else: + new_pair = "%s=%s" % ( + urllib.parse.quote(str(key)), + urllib.parse.quote(str(value)), + ) + pairs.append(new_pair) + return pairs + + return "&".join(recursion(d)) + + def encoded_query_params(self, query_params): + if not query_params: + return "" + + final_query_params = "" + for key, value in query_params: + if isinstance(value, dict): + nested_param = {} + nested_param[key] = value + final_query_params += self.recursive_urlencode(nested_param) + query_params.remove((key, value)) + + if query_params: + if final_query_params: + final_query_params += "&" + final_query_params += urlencode(query_params) + + return "?" + final_query_params + def request( self, method, @@ -170,8 +225,7 @@ def request( try: # For `POST`, `PUT`, `PATCH`, `OPTIONS`, `DELETE` if method in ["POST", "PUT", "PATCH", "OPTIONS", "DELETE"]: - if query_params: - url += "?" + urlencode(query_params) + url += self.encoded_query_params(query_params) if re.search("json", headers["Content-Type"], re.IGNORECASE): request_body = None if body is not None: @@ -231,10 +285,10 @@ def request( raise ApiException(status=0, reason=msg) # For `GET`, `HEAD` else: + url += self.encoded_query_params(query_params) r = self.pool_manager.request( method, url, - fields=query_params, preload_content=_preload_content, timeout=timeout, headers=headers, diff --git a/setup.py b/setup.py index 2944ae0..f4070af 100644 --- a/setup.py +++ b/setup.py @@ -12,7 +12,7 @@ from setuptools import setup, find_packages # noqa: H301 NAME = "patch-api" -VERSION = "1.8.0" +VERSION = "1.9.0" # To install the library, run the following # # python setup.py install diff --git a/test/test_estimates_api.py b/test/test_estimates_api.py index 34be1fb..84f0c9b 100644 --- a/test/test_estimates_api.py +++ b/test/test_estimates_api.py @@ -53,8 +53,8 @@ def test_create_and_retrieve_flight_estimate(self): distance_m=distance_m, create_order=True ) self.assertEqual(estimate.data.type, "flight") - self.assertEqual(estimate.data.order.mass_g, 1031697) - self.assertEqual(estimate.data.mass_g, 1031697) + self.assertEqual(estimate.data.order.mass_g, 1000622) + self.assertEqual(estimate.data.mass_g, 1000622) retrieved_estimate = self.api.retrieve_estimate(id=estimate.data.id) self.assertTrue(retrieved_estimate) @@ -75,7 +75,7 @@ def test_create_and_retrieve_shipping_estimate(self): ) self.assertEqual(estimate.data.order, None) self.assertEqual(estimate.data.type, "shipping") - self.assertEqual(estimate.data.mass_g, 373) + self.assertEqual(estimate.data.mass_g, 249) retrieved_estimate = self.api.retrieve_estimate(id=estimate.data.id) self.assertTrue(retrieved_estimate) diff --git a/test/test_orders_api.py b/test/test_orders_api.py index ba1b37c..2520384 100644 --- a/test/test_orders_api.py +++ b/test/test_orders_api.py @@ -77,6 +77,23 @@ def test_retrieve_orders(self): self.assertEqual(retrieved_order.metadata, {}) self.assertTrue(isinstance(retrieved_order.allocations, list)) + def test_create_and_retrieve_order_via_metadata(self): + """Test case for create_order and retrieve_orders with metadata""" + mass_g = 100 + metadata = {"external_id": "abc-123"} + order = self.api.create_order(mass_g=mass_g, metadata=metadata) + + self.assertTrue(order) + self.assertEqual(order.data.mass_g, 100) + self.assertEqual(order.data.metadata, {"external_id": "abc-123"}) + + retrieved_orders = self.api.retrieve_orders( + metadata={"external_id": "abc-"} + ).data + self.assertGreater(len(retrieved_orders), 0) + for retrieved_order in retrieved_orders: + self.assertTrue("external_id" in retrieved_order.metadata) + if __name__ == "__main__": unittest.main() diff --git a/test/test_projects_api.py b/test/test_projects_api.py index a01aa6e..11f2d90 100644 --- a/test/test_projects_api.py +++ b/test/test_projects_api.py @@ -55,7 +55,7 @@ def test_retrieve_projects(self): self.assertGreater(project.average_price_per_tonne_cents_usd, 0) self.assertGreater(project.remaining_mass_g, 0) self.assertEqual(project.standard, None) - self.assertEqual(project.name, "Patch's Biomass Test Offset Project") + self.assertRegex(project.name, r"Test Offset Project") self.assertTrue(project.description) self.assertEqual(project.country, "US") self.assertEqual(project.type, "biomass") diff --git a/test/test_rest.py b/test/test_rest.py new file mode 100644 index 0000000..490b7a9 --- /dev/null +++ b/test/test_rest.py @@ -0,0 +1,58 @@ +from __future__ import absolute_import + +from patch_api.configuration import Configuration +from patch_api import rest + +import unittest +import os + +from patch_api.api_client import ApiClient + + +class TestRESTClientObject(unittest.TestCase): + def test_recursive_urlencode(self): + """Test that the correct query params get encoded""" + + api_key = os.environ.get("SANDBOX_API_KEY") + configuration = Configuration() + client = rest.RESTClientObject(api_key, configuration) + + encoded_params = client.recursive_urlencode({}) + self.assertEqual(encoded_params, "") + + encoded_params = client.recursive_urlencode({"page": 1}) + self.assertEqual(encoded_params, "page=1") + + encoded_params = client.recursive_urlencode({"string": "value"}) + self.assertEqual(encoded_params, "string=value") + + encoded_params = client.recursive_urlencode( + {"multiple": "values", "top": "level"} + ) + self.assertEqual(encoded_params, "multiple=values&top=level") + + encoded_params = client.recursive_urlencode({"metadata": {"some": "arg"}}) + self.assertEqual(encoded_params, "metadata[some]=arg") + + encoded_params = client.recursive_urlencode( + {"deeply": {"nested": {"hash": {"of": "args"}}}} + ) + self.assertEqual(encoded_params, "deeply[nested][hash][of]=args") + + def test_encoded_query_params(self): + """Test that the correct query params get encoded""" + + api_key = os.environ.get("SANDBOX_API_KEY") + configuration = Configuration() + client = rest.RESTClientObject(api_key, configuration) + + encoded_params = client.encoded_query_params([]) + self.assertEqual(encoded_params, "") + + encoded_params = client.encoded_query_params([("mass_g", 100)]) + self.assertEqual(encoded_params, "?mass_g=100") + + encoded_params = client.encoded_query_params( + [("mass_g", 100), ("metadata", {"external_id": "abc-123"})] + ) + self.assertEqual(encoded_params, "?metadata[external_id]=abc-123&mass_g=100")