From 50abfb17f8d7ef359e0a54e0d536332462714384 Mon Sep 17 00:00:00 2001 From: timwis Date: Thu, 19 Nov 2015 07:29:57 -0500 Subject: [PATCH 01/16] Add create, set_public, publish --- sodapy/__init__.py | 54 +++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 49 insertions(+), 5 deletions(-) diff --git a/sodapy/__init__.py b/sodapy/__init__.py index 35fb5c1..8132bad 100644 --- a/sodapy/__init__.py +++ b/sodapy/__init__.py @@ -76,8 +76,51 @@ def authentication_validation(self, username, password, access_token): " OAuth2.0. Please use only one authentication" " method.") - def create(self, file_object): - raise NotImplementedError() + def create(self, name, **kwargs): + ''' + Create a dataset, including the field types. Optionally, specify args: + description : description of the dataset + category : must exist in /admin/metadata + row_identifier : field name of primary key + public : whether or not the dataset should be publicly accessible + published : whether to keep the dataset in the "staging" phase or publish + ''' + public = kwargs.pop("public", False) + published = kwargs.pop("published", False) + + payload = { + "name": name, + "description": kwargs.pop("description", None), + "category": kwargs.pop("category", None) + } + if("row_identifier" in kwargs): + payload.metadata = { + "rowIdentifier": kwargs.pop("row_identifier", None) + } + + payload.update(kwargs) + payload = _clear_empty_values(payload) + + return self._perform_update("post", "/api/views.json", payload) + + def set_public(self, resource): + ''' + After creating a dataset, use this method to make it public + ''' + params = { + 'method': 'setPermission', + 'value': 'public.read' + } + resource = resource.rsplit("/", 1)[-1] # just get the dataset id + + return self._perform_request("put", "/api/views/" + resource, params=params) + + def publish(self, resource): + ''' + After creating a dataset, use this method to publish it + ''' + resource = resource.split("/", 1)[-1].split(".")[0] # just get the dataset id + return self._perform_request("post", "/api/views/" + resource + "/publication.json") def get(self, resource, **kwargs): ''' @@ -145,7 +188,7 @@ def replace(self, resource, payload): return self._perform_update("put", resource, payload) def _perform_update(self, method, resource, payload): - if isinstance(payload, list): + if isinstance(payload, list) or isinstance(payload, dict): response = self._perform_request(method, resource, data=json.dumps(payload)) elif isinstance(payload, file): @@ -195,8 +238,9 @@ def _perform_request(self, request_type, resource, **kwargs): if response.status_code not in (200, 202): _raise_for_status(response) - # deletes have no content body, simply return the whole response - if request_type == "delete": + # when responses have no content body (ie. delete, set_public), simply + # return the whole response + if not len(response.text): return response # for other request types, return most useful data From efc0f0c25c867a74914027fbb0e3d61efbc97528 Mon Sep 17 00:00:00 2001 From: timwis Date: Thu, 19 Nov 2015 07:38:33 -0500 Subject: [PATCH 02/16] Add support for columns to create --- sodapy/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sodapy/__init__.py b/sodapy/__init__.py index 8132bad..91b9787 100644 --- a/sodapy/__init__.py +++ b/sodapy/__init__.py @@ -80,6 +80,7 @@ def create(self, name, **kwargs): ''' Create a dataset, including the field types. Optionally, specify args: description : description of the dataset + columns : list of columns (see docs/tests for list structure) category : must exist in /admin/metadata row_identifier : field name of primary key public : whether or not the dataset should be publicly accessible @@ -91,7 +92,8 @@ def create(self, name, **kwargs): payload = { "name": name, "description": kwargs.pop("description", None), - "category": kwargs.pop("category", None) + "category": kwargs.pop("category", None), + "columns": kwargs.pop("columns", None) } if("row_identifier" in kwargs): payload.metadata = { From 7219569f6b8984df4d96281f4fce73313745f48a Mon Sep 17 00:00:00 2001 From: timwis Date: Fri, 20 Nov 2015 07:19:44 -0500 Subject: [PATCH 03/16] Use proper accessor, double quotes --- sodapy/__init__.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/sodapy/__init__.py b/sodapy/__init__.py index 91b9787..74d1f35 100644 --- a/sodapy/__init__.py +++ b/sodapy/__init__.py @@ -95,8 +95,9 @@ def create(self, name, **kwargs): "category": kwargs.pop("category", None), "columns": kwargs.pop("columns", None) } + if("row_identifier" in kwargs): - payload.metadata = { + payload["metadata"] = { "rowIdentifier": kwargs.pop("row_identifier", None) } @@ -110,8 +111,8 @@ def set_public(self, resource): After creating a dataset, use this method to make it public ''' params = { - 'method': 'setPermission', - 'value': 'public.read' + "method": "setPermission", + "value": "public.read" } resource = resource.rsplit("/", 1)[-1] # just get the dataset id From abb27ca2f08a90461d05ccd8f1572801d189b935 Mon Sep 17 00:00:00 2001 From: timwis Date: Fri, 20 Nov 2015 07:26:47 -0500 Subject: [PATCH 04/16] Add test for create() #3 --- tests/test_data/create_foobar.txt | 65 +++++++++++++++++++++++++++++++ tests/test_soda.py | 39 +++++++++++++++++-- 2 files changed, 101 insertions(+), 3 deletions(-) create mode 100644 tests/test_data/create_foobar.txt diff --git a/tests/test_data/create_foobar.txt b/tests/test_data/create_foobar.txt new file mode 100644 index 0000000..ba28411 --- /dev/null +++ b/tests/test_data/create_foobar.txt @@ -0,0 +1,65 @@ +{ + "id": "2frc-hyvj", + "name": "Foo Bar", + "averageRating": 0, + "createdAt": 1448018696, + "description": "test dataset", + "downloadCount": 0, + "newBackend": false, + "numberOfComments": 0, + "oid": 14929734, + "publicationAppendEnabled": false, + "publicationGroup": 5638965, + "publicationStage": "unpublished", + "rowIdentifierColumnId": 230641051, + "rowsUpdatedAt": 1448018697, + "rowsUpdatedBy": "gxfh-uqsf", + "tableId": 5638965, + "totalTimesRated": 0, + "viewCount": 0, + "viewLastModified": 1448018697, + "viewType": "tabular", + "columns": [ + { + "id": 230641050, + "name": "Foo", + "dataTypeName": "text", + "fieldName": "foo", + "position": 1, + "renderTypeName": "text", + "tableColumnId": 32762225, + "format": {} + }, + { + "id": 230641051, + "name": "Bar", + "dataTypeName": "number", + "fieldName": "bar", + "position": 2, + "renderTypeName": "number", + "tableColumnId": 32762226, + "format": {} + } + ], + "metadata": { + "rowIdentifier": 230641051 + }, + "owner": {}, + "query": {}, + "rights": [ + "read", + "write", + "add", + "delete", + "grant", + "add_column", + "remove_column", + "update_column", + "update_view", + "delete_view" + ], + "tableAuthor": {}, + "flags": [ + "default" + ] +} \ No newline at end of file diff --git a/tests/test_soda.py b/tests/test_soda.py index 3f69cd8..0f39cdd 100644 --- a/tests/test_soda.py +++ b/tests/test_soda.py @@ -124,14 +124,47 @@ def test_delete(): assert isinstance(e, requests_mock.exceptions.NoMockAddress) finally: client.close() - + +def test_create(): + mock_adapter = {} + mock_adapter["prefix"] = PREFIX + adapter = requests_mock.Adapter() + mock_adapter["adapter"] = adapter + client = Socrata(DOMAIN, APPTOKEN, username=USERNAME, password=PASSWORD, + session_adapter=mock_adapter) + + response_data = "create_foobar.txt" + resource = "/api/views.json" + set_up_mock(adapter, "POST", response_data, 200, resource=resource) + + columns = [ + {"fieldName": "foo", "name": "Foo", "dataTypeName": "text"}, + {"fieldName": "bar", "name": "Bar", "dataTypeName": "number"} + ] + response = client.create("Foo Bar", description="test dataset", + columns=columns, row_identifier="bar") + + request = adapter.request_history[0] + request_payload = json.loads(request.text) # can't figure out how to use .json + + # Test request payload + for dataset_key in ["name", "description", "columns"]: + assert dataset_key in request_payload + + for column_key in ["fieldName", "name", "dataTypeName"]: + assert column_key in request_payload["columns"][0] + + # Test response + assert isinstance(response, dict) + assert len(response.get("id")) == 9 + client.close() def set_up_mock(adapter, method, response, response_code, - reason="OK", auth=None): + reason="OK", auth=None, resource=PATH): path = os.path.join(TEST_DATA_PATH, response) with open(path, "rb") as f: body = json.load(f) - uri = "{0}://{1}{2}".format(PREFIX, DOMAIN, PATH) + uri = "{0}://{1}{2}".format(PREFIX, DOMAIN, resource) headers = { "content-type": "application/json; charset=utf-8" } From f09be89978a8e29a438b2741caca9b03255db779 Mon Sep 17 00:00:00 2001 From: timwis Date: Sat, 21 Nov 2015 09:48:24 -0500 Subject: [PATCH 05/16] Use http for mock protocol, add test for set_public(). #3 --- sodapy/__init__.py | 8 ++++---- tests/test_data/empty.txt | 0 tests/test_soda.py | 32 ++++++++++++++++++++++++++++---- 3 files changed, 32 insertions(+), 8 deletions(-) create mode 100644 tests/test_data/empty.txt diff --git a/sodapy/__init__.py b/sodapy/__init__.py index 74d1f35..4904c51 100644 --- a/sodapy/__init__.py +++ b/sodapy/__init__.py @@ -62,7 +62,7 @@ def __init__(self, domain, app_token, username=None, password=None, session_adapter["adapter"]) self.uri_prefix = session_adapter["prefix"] else: - self.uri_prefix = "https" + self.uri_prefix = "https://" def authentication_validation(self, username, password, access_token): ''' @@ -230,20 +230,20 @@ def _perform_request(self, request_type, resource, **kwargs): raise Exception("Unknown request type. Supported request types are" ": {0}".format(", ".join(request_type_methods))) - uri = "{0}://{1}{2}".format(self.uri_prefix, self.domain, resource) + uri = "{0}{1}{2}".format(self.uri_prefix, self.domain, resource) # set a timeout, just to be safe kwargs["timeout"] = 10 response = getattr(self.session, request_type)(uri, **kwargs) - + # handle errors if response.status_code not in (200, 202): _raise_for_status(response) # when responses have no content body (ie. delete, set_public), simply # return the whole response - if not len(response.text): + if not response.text: return response # for other request types, return most useful data diff --git a/tests/test_data/empty.txt b/tests/test_data/empty.txt new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_soda.py b/tests/test_soda.py index 0f39cdd..4bf0e31 100644 --- a/tests/test_soda.py +++ b/tests/test_soda.py @@ -7,7 +7,7 @@ import json -PREFIX = "mock" +PREFIX = "http://" DOMAIN = "fakedomain.com" PATH = "/songs.json" APPTOKEN = "FakeAppToken" @@ -113,7 +113,7 @@ def test_delete(): client = Socrata(DOMAIN, APPTOKEN, username=USERNAME, password=PASSWORD, session_adapter=mock_adapter) - uri = "{0}://{1}{2}".format(PREFIX, DOMAIN, PATH) + uri = "{0}{1}{2}".format(PREFIX, DOMAIN, PATH) adapter.register_uri("DELETE", uri, status_code=200) response = client.delete(PATH) assert response.status_code == 200 @@ -159,12 +159,36 @@ def test_create(): assert len(response.get("id")) == 9 client.close() +def test_set_public(): + mock_adapter = {} + mock_adapter["prefix"] = PREFIX + adapter = requests_mock.Adapter() + mock_adapter["adapter"] = adapter + client = Socrata(DOMAIN, APPTOKEN, username=USERNAME, password=PASSWORD, + session_adapter=mock_adapter) + + response_data = "empty.txt" + resource = "/api/views" + PATH + set_up_mock(adapter, "PUT", response_data, 200, resource=resource) + + response = client.set_public(PATH) + + request = adapter.request_history[0] + assert "method" in request.qs + assert "value" in request.qs + + assert response.status_code == 200 + def set_up_mock(adapter, method, response, response_code, reason="OK", auth=None, resource=PATH): path = os.path.join(TEST_DATA_PATH, response) with open(path, "rb") as f: - body = json.load(f) - uri = "{0}://{1}{2}".format(PREFIX, DOMAIN, resource) + try: + body = json.load(f) + except ValueError: + body = None + + uri = "{0}{1}{2}".format(PREFIX, DOMAIN, resource) headers = { "content-type": "application/json; charset=utf-8" } From 7ea0f16183274f2446e60c44f85bfaf7f23a4f89 Mon Sep 17 00:00:00 2001 From: timwis Date: Sun, 22 Nov 2015 13:44:07 -0500 Subject: [PATCH 06/16] Touch-ups to set_public test --- tests/test_soda.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/test_soda.py b/tests/test_soda.py index 4bf0e31..2f76487 100644 --- a/tests/test_soda.py +++ b/tests/test_soda.py @@ -157,6 +157,7 @@ def test_create(): # Test response assert isinstance(response, dict) assert len(response.get("id")) == 9 + client.close() def test_set_public(): @@ -171,13 +172,16 @@ def test_set_public(): resource = "/api/views" + PATH set_up_mock(adapter, "PUT", response_data, 200, resource=resource) + # Test response response = client.set_public(PATH) + assert response.status_code == 200 + # Test request request = adapter.request_history[0] assert "method" in request.qs assert "value" in request.qs - assert response.status_code == 200 + client.close() def set_up_mock(adapter, method, response, response_code, reason="OK", auth=None, resource=PATH): From d12828001df43bb7e6a48b43bb164b16e4f8b85a Mon Sep 17 00:00:00 2001 From: timwis Date: Sun, 22 Nov 2015 14:08:38 -0500 Subject: [PATCH 07/16] add test for publish() --- tests/test_soda.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/tests/test_soda.py b/tests/test_soda.py index 2f76487..5f71af6 100644 --- a/tests/test_soda.py +++ b/tests/test_soda.py @@ -157,7 +157,6 @@ def test_create(): # Test response assert isinstance(response, dict) assert len(response.get("id")) == 9 - client.close() def test_set_public(): @@ -180,7 +179,23 @@ def test_set_public(): request = adapter.request_history[0] assert "method" in request.qs assert "value" in request.qs + client.close() + +def test_publish(): + mock_adapter = {} + mock_adapter["prefix"] = PREFIX + adapter = requests_mock.Adapter() + mock_adapter["adapter"] = adapter + client = Socrata(DOMAIN, APPTOKEN, username=USERNAME, password=PASSWORD, + session_adapter=mock_adapter) + response_data = "create_foobar.txt" + resource = "/api/views/songs/publication.json" # publish() removes .json + set_up_mock(adapter, "POST", response_data, 200, resource=resource) + + response = client.publish("/songs.json") # hard-coded so request uri is matched + assert isinstance(response, dict) + assert len(response.get("id")) == 9 client.close() def set_up_mock(adapter, method, response, response_code, From 30586a64476100c2892a9c0ebb5b8880ba7b46fa Mon Sep 17 00:00:00 2001 From: timwis Date: Sun, 22 Nov 2015 14:18:35 -0500 Subject: [PATCH 08/16] create() uses kwargs in payload implicitly. add test & doc for tags. Closes #1 --- sodapy/__init__.py | 14 +++++--------- tests/test_soda.py | 5 +++-- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/sodapy/__init__.py b/sodapy/__init__.py index 4904c51..6cd1dda 100644 --- a/sodapy/__init__.py +++ b/sodapy/__init__.py @@ -78,23 +78,19 @@ def authentication_validation(self, username, password, access_token): def create(self, name, **kwargs): ''' - Create a dataset, including the field types. Optionally, specify args: + Create a dataset, including the field types. Optionally, specify args such as: description : description of the dataset columns : list of columns (see docs/tests for list structure) category : must exist in /admin/metadata + tags : array of tag strings row_identifier : field name of primary key - public : whether or not the dataset should be publicly accessible - published : whether to keep the dataset in the "staging" phase or publish + public : whether to set permissions to public + published : whether to keep as working copy or publish ''' public = kwargs.pop("public", False) published = kwargs.pop("published", False) - payload = { - "name": name, - "description": kwargs.pop("description", None), - "category": kwargs.pop("category", None), - "columns": kwargs.pop("columns", None) - } + payload = {"name": name} if("row_identifier" in kwargs): payload["metadata"] = { diff --git a/tests/test_soda.py b/tests/test_soda.py index 5f71af6..902e379 100644 --- a/tests/test_soda.py +++ b/tests/test_soda.py @@ -141,14 +141,15 @@ def test_create(): {"fieldName": "foo", "name": "Foo", "dataTypeName": "text"}, {"fieldName": "bar", "name": "Bar", "dataTypeName": "number"} ] + tags = ["foo", "bar"] response = client.create("Foo Bar", description="test dataset", - columns=columns, row_identifier="bar") + columns=columns, tags=tags, row_identifier="bar") request = adapter.request_history[0] request_payload = json.loads(request.text) # can't figure out how to use .json # Test request payload - for dataset_key in ["name", "description", "columns"]: + for dataset_key in ["name", "description", "columns", "tags"]: assert dataset_key in request_payload for column_key in ["fieldName", "name", "dataTypeName"]: From 7c744608002ab597438c3283af24e11eb5df03fc Mon Sep 17 00:00:00 2001 From: timwis Date: Sun, 22 Nov 2015 15:14:36 -0500 Subject: [PATCH 09/16] rename set_public to set_permission and simplify test --- sodapy/__init__.py | 7 ++++--- tests/test_soda.py | 8 ++++---- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/sodapy/__init__.py b/sodapy/__init__.py index 6cd1dda..3d0c7b5 100644 --- a/sodapy/__init__.py +++ b/sodapy/__init__.py @@ -102,13 +102,14 @@ def create(self, name, **kwargs): return self._perform_update("post", "/api/views.json", payload) - def set_public(self, resource): + def set_permission(self, resource, permission="private"): ''' - After creating a dataset, use this method to make it public + Set a dataset's permissions to private or public + Options are private, public ''' params = { "method": "setPermission", - "value": "public.read" + "value": "public.read" if permission == "public" else permission } resource = resource.rsplit("/", 1)[-1] # just get the dataset id diff --git a/tests/test_soda.py b/tests/test_soda.py index 902e379..ca226a3 100644 --- a/tests/test_soda.py +++ b/tests/test_soda.py @@ -160,7 +160,7 @@ def test_create(): assert len(response.get("id")) == 9 client.close() -def test_set_public(): +def test_set_permission(): mock_adapter = {} mock_adapter["prefix"] = PREFIX adapter = requests_mock.Adapter() @@ -173,13 +173,13 @@ def test_set_public(): set_up_mock(adapter, "PUT", response_data, 200, resource=resource) # Test response - response = client.set_public(PATH) + response = client.set_permission(PATH, permission="public") assert response.status_code == 200 # Test request request = adapter.request_history[0] - assert "method" in request.qs - assert "value" in request.qs + qs = request.url.split("?")[-1] + assert qs == "method=setPermission&value=public.read" client.close() def test_publish(): From 01f25fe5c530029d4923d5b3322eed5e7f896b4d Mon Sep 17 00:00:00 2001 From: timwis Date: Sun, 22 Nov 2015 15:15:00 -0500 Subject: [PATCH 10/16] Tidy method descriptors --- sodapy/__init__.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/sodapy/__init__.py b/sodapy/__init__.py index 3d0c7b5..b3c8d1b 100644 --- a/sodapy/__init__.py +++ b/sodapy/__init__.py @@ -84,8 +84,6 @@ def create(self, name, **kwargs): category : must exist in /admin/metadata tags : array of tag strings row_identifier : field name of primary key - public : whether to set permissions to public - published : whether to keep as working copy or publish ''' public = kwargs.pop("public", False) published = kwargs.pop("published", False) @@ -117,7 +115,8 @@ def set_permission(self, resource, permission="private"): def publish(self, resource): ''' - After creating a dataset, use this method to publish it + The create() method creates a dataset in a "working copy" state. + This method publishes it. ''' resource = resource.split("/", 1)[-1].split(".")[0] # just get the dataset id return self._perform_request("post", "/api/views/" + resource + "/publication.json") From b7ee184ed04a8760f40a2e303e8407ae86512197 Mon Sep 17 00:00:00 2001 From: timwis Date: Sun, 22 Nov 2015 15:20:44 -0500 Subject: [PATCH 11/16] comment fix --- sodapy/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sodapy/__init__.py b/sodapy/__init__.py index b3c8d1b..7734983 100644 --- a/sodapy/__init__.py +++ b/sodapy/__init__.py @@ -237,8 +237,8 @@ def _perform_request(self, request_type, resource, **kwargs): if response.status_code not in (200, 202): _raise_for_status(response) - # when responses have no content body (ie. delete, set_public), simply - # return the whole response + # when responses have no content body (ie. delete, set_permission), + # simply return the whole response if not response.text: return response From f107706d80b2e2fd23a9c5c451f45e250b4e8052 Mon Sep 17 00:00:00 2001 From: timwis Date: Sun, 22 Nov 2015 15:23:41 -0500 Subject: [PATCH 12/16] Support /resource prefix in publish method --- sodapy/__init__.py | 2 +- tests/test_soda.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sodapy/__init__.py b/sodapy/__init__.py index 7734983..2755087 100644 --- a/sodapy/__init__.py +++ b/sodapy/__init__.py @@ -118,7 +118,7 @@ def publish(self, resource): The create() method creates a dataset in a "working copy" state. This method publishes it. ''' - resource = resource.split("/", 1)[-1].split(".")[0] # just get the dataset id + resource = resource.rsplit("/", 1)[-1].split(".")[0] # just get the dataset id return self._perform_request("post", "/api/views/" + resource + "/publication.json") def get(self, resource, **kwargs): diff --git a/tests/test_soda.py b/tests/test_soda.py index ca226a3..a4cc7d2 100644 --- a/tests/test_soda.py +++ b/tests/test_soda.py @@ -194,7 +194,7 @@ def test_publish(): resource = "/api/views/songs/publication.json" # publish() removes .json set_up_mock(adapter, "POST", response_data, 200, resource=resource) - response = client.publish("/songs.json") # hard-coded so request uri is matched + response = client.publish("/resource/songs.json") # hard-coded so request uri is matched assert isinstance(response, dict) assert len(response.get("id")) == 9 client.close() From 9e9a66a309491a4616ed00e485fff1406378cb4b Mon Sep 17 00:00:00 2001 From: timwis Date: Sun, 22 Nov 2015 15:36:08 -0500 Subject: [PATCH 13/16] Add usage to README files. Closes #4 --- README | 17 +++++++++++++++++ README.md | 17 +++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/README b/README index ad75248..00a910c 100644 --- a/README +++ b/README @@ -37,6 +37,23 @@ Retrieving data is easy! Use SQL-style keyword args to filter data, or lookup an >>> client.get("/resource/nimj-3ivp/193.json", exclude_system_fields=False) {u'geolocation': {u'latitude': u'21.6711', u'needs_recoding': False, u'longitude': u'142.9236'}, u'version': u'C', u':updated_at': 1348778988, u'number_of_stations': u'136', u'region': u'Mariana Islands region', u':created_meta': u'21484', u'occurred_at': u'2012-09-13T11:19:07', u':id': 193, u'source': u'us', u'depth': u'300.70', u'magnitude': u'4.4', u':meta': u'{\n}', u':updated_meta': u'21484', u':position': 193, u'earthquake_id': u'c000cmsq', u':created_at': 1348778988} +Create a dataset + + >>> columns = [{"fieldName": "delegation", "name": "Delegation", "dataTypeName": "text"}, {"fieldName": "members", "name": "Members", "dataTypeName": "number"}] + >>> tags = ["politics", "geography"] + >>> client.create("Delegates", description="List of delegates", columns=columns, row_identifier="delegation", tags=tags, category="Transparency") + {u'id': u'2frc-hyvj', u'name': u'Foo Bar', u'description': u'test dataset', u'publicationStage': u'unpublished', u'columns': [ { u'name': u'Foo', u'dataTypeName': u'text', u'fieldName': u'foo', ... }, { u'name': u'Bar', u'dataTypeName': u'number', u'fieldName': u'bar', ... } ], u'metadata': { u'rowIdentifier': 230641051 }, ... } + +Publish a dataset after creating it (take it out of 'working copy' mode) + + >>> client.publish("/resource/eb9n-hr43.json") + {u'id': u'2frc-hyvj', u'name': u'Foo Bar', u'description': u'test dataset', u'publicationStage': u'unpublished', u'columns': [ { u'name': u'Foo', u'dataTypeName': u'text', u'fieldName': u'foo', ... }, { u'name': u'Bar', u'dataTypeName': u'number', u'fieldName': u'bar', ... } ], u'metadata': { u'rowIdentifier': 230641051 }, ... } + +Set the permissions of a dataset to public or private + + >>> client.set_permission("/resource/eb9n-hr43.json", "public") + + Create a new row in an existing dataset >>> data = [{'Delegation': 'AJU', 'Name': 'Alaska', 'Key': 'AL', 'Entity': 'Juneau'}] diff --git a/README.md b/README.md index a87ca2f..6c51eb2 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,23 @@ Retrieving data is easy! Use SQL-style keyword args to filter data, or lookup an >>> client.get("/resource/nimj-3ivp/193.json", exclude_system_fields=False) {u'geolocation': {u'latitude': u'21.6711', u'needs_recoding': False, u'longitude': u'142.9236'}, u'version': u'C', u':updated_at': 1348778988, u'number_of_stations': u'136', u'region': u'Mariana Islands region', u':created_meta': u'21484', u'occurred_at': u'2012-09-13T11:19:07', u':id': 193, u'source': u'us', u'depth': u'300.70', u'magnitude': u'4.4', u':meta': u'{\n}', u':updated_meta': u'21484', u':position': 193, u'earthquake_id': u'c000cmsq', u':created_at': 1348778988} +Create a dataset + + >>> columns = [{"fieldName": "delegation", "name": "Delegation", "dataTypeName": "text"}, {"fieldName": "members", "name": "Members", "dataTypeName": "number"}] + >>> tags = ["politics", "geography"] + >>> client.create("Delegates", description="List of delegates", columns=columns, row_identifier="delegation", tags=tags, category="Transparency") + {u'id': u'2frc-hyvj', u'name': u'Foo Bar', u'description': u'test dataset', u'publicationStage': u'unpublished', u'columns': [ { u'name': u'Foo', u'dataTypeName': u'text', u'fieldName': u'foo', ... }, { u'name': u'Bar', u'dataTypeName': u'number', u'fieldName': u'bar', ... } ], u'metadata': { u'rowIdentifier': 230641051 }, ... } + +Publish a dataset after creating it (take it out of 'working copy' mode) + + >>> client.publish("/resource/eb9n-hr43.json") + {u'id': u'2frc-hyvj', u'name': u'Foo Bar', u'description': u'test dataset', u'publicationStage': u'unpublished', u'columns': [ { u'name': u'Foo', u'dataTypeName': u'text', u'fieldName': u'foo', ... }, { u'name': u'Bar', u'dataTypeName': u'number', u'fieldName': u'bar', ... } ], u'metadata': { u'rowIdentifier': 230641051 }, ... } + +Set the permissions of a dataset to public or private + + >>> client.set_permission("/resource/eb9n-hr43.json", "public") + + Create a new row in an existing dataset >>> data = [{'Delegation': 'AJU', 'Name': 'Alaska', 'Key': 'AL', 'Entity': 'Juneau'}] From 3347faf8d6223e591158627a58478f89df5d5928 Mon Sep 17 00:00:00 2001 From: timwis Date: Sun, 22 Nov 2015 15:42:44 -0500 Subject: [PATCH 14/16] Tidy up test_soda.py --- tests/test_soda.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_soda.py b/tests/test_soda.py index a4cc7d2..45ff244 100644 --- a/tests/test_soda.py +++ b/tests/test_soda.py @@ -173,7 +173,7 @@ def test_set_permission(): set_up_mock(adapter, "PUT", response_data, 200, resource=resource) # Test response - response = client.set_permission(PATH, permission="public") + response = client.set_permission(PATH, "public") assert response.status_code == 200 # Test request @@ -206,7 +206,7 @@ def set_up_mock(adapter, method, response, response_code, try: body = json.load(f) except ValueError: - body = None + body = None # for case of empty file (test_set_permission) uri = "{0}{1}{2}".format(PREFIX, DOMAIN, resource) headers = { From b8a532bed2775ffcaf989a2d4addd07e67c25166 Mon Sep 17 00:00:00 2001 From: timwis Date: Sun, 22 Nov 2015 15:46:50 -0500 Subject: [PATCH 15/16] Use https prefix in test_soda --- tests/test_soda.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_soda.py b/tests/test_soda.py index 45ff244..6fe92bc 100644 --- a/tests/test_soda.py +++ b/tests/test_soda.py @@ -7,7 +7,7 @@ import json -PREFIX = "http://" +PREFIX = "https://" DOMAIN = "fakedomain.com" PATH = "/songs.json" APPTOKEN = "FakeAppToken" From be0851d5f817f13ea089b972f5e51e1a14e15a9a Mon Sep 17 00:00:00 2001 From: timwis Date: Sun, 22 Nov 2015 17:57:07 -0500 Subject: [PATCH 16/16] remove whitespace, remove limitation in readme.md --- README.md | 3 --- sodapy/__init__.py | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/README.md b/README.md index 6c51eb2..4320f5f 100644 --- a/README.md +++ b/README.md @@ -94,6 +94,3 @@ Wrap up when you're finished. ## Run tests $ ./runtests tests/ - -## TODO and known issues -- Currently, the client does not support dataset creation. A new import API is under construction, and being tracked [here](https://github.com/socrata/soda-ruby/issues/13). diff --git a/sodapy/__init__.py b/sodapy/__init__.py index 2755087..6a90f3c 100644 --- a/sodapy/__init__.py +++ b/sodapy/__init__.py @@ -232,7 +232,7 @@ def _perform_request(self, request_type, resource, **kwargs): kwargs["timeout"] = 10 response = getattr(self.session, request_type)(uri, **kwargs) - + # handle errors if response.status_code not in (200, 202): _raise_for_status(response)