Skip to content

Commit 4c55d0a

Browse files
author
Rafa de la Torre
authored
Merge pull request #87 from CartoDB/copy-endpoints-support
Copy endpoints support
2 parents 7aeeab4 + d006740 commit 4c55d0a

File tree

17 files changed

+744
-15
lines changed

17 files changed

+744
-15
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ htmlcov/
4444
nosetests.xml
4545
coverage.xml
4646
*,cover
47+
.pytest_cache/
4748

4849
# Translations
4950
*.mo

CONTRIBUTING.md

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@ Contributions are totally welcome. However, contributors must sign a Contributor
44

55
## Release process
66

7-
1. Update version number and information at `setup.py`, `conf.py` and `NEWS.md`.
8-
2. You must be maintainer at [carto pypi repo](https://pypi.python.org/pypi/carto/).
9-
3. Prepare a `~/.pypirc` file:
7+
1. Make sure documentation is generated properly. See the [doc](https://github.com/CartoDB/carto-python/tree/master/doc) directory for more information.
8+
2. Update version number and information at `setup.py`, `conf.py` and `NEWS.md`.
9+
3. You must be maintainer at [carto pypi repo](https://pypi.python.org/pypi/carto/).
10+
4. Prepare a `~/.pypirc` file:
1011

1112
```
1213
[distutils]
@@ -23,7 +24,7 @@ username=your_username
2324
password=your_password
2425
```
2526

26-
4. Upload the package to the test repository: `python setup.py sdist upload -r pypitest`.
27-
5. Install it in a new environment: `pip install --index-url=https://test.pypi.org/simple --extra-index-url=https://pypi.org/simple carto`.
28-
6. Test it.
29-
7. Release it: `python setup.py sdist upload -r pypi`.
27+
5. Upload the package to the test repository: `python setup.py sdist upload -r pypitest`.
28+
6. Install it in a new environment: `pip install --index-url=https://test.pypi.org/simple --extra-index-url=https://pypi.org/simple carto`.
29+
7. Test it.
30+
8. Release it: `python setup.py sdist upload -r pypi`.

NEWS

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
1+
Mmm-dd-yyyy: version 1.3.0
2+
- Added a `CopySQLClient` for efficient streaming of data to and from CARTO (#87)
3+
- Fixed CI tests
4+
- Added a request header `User-Agent: carto-python-sdk/x.y.z` where `x.y.z` is the current version of the library.
5+
- Added a `client=cps-x.y.z` parameter to requests, where `x.y.z` is the current version of the library.
6+
7+
Feb-22-2028: version 1.2.2
8+
- Updated release instructions in CONTRIBUTING.md
9+
110
Feb-21-2018: version 1.2.1
211
- Added AuthAPIClient class for future Auth API usage and API key validation (#69)
312

README.md

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,62 @@ updateJob = batchSQLClient.update(job_id, NEW_QUERY)
139139
cancelJob = batchSQLClient.cancel(job_id)
140140
```
141141

142+
**COPY queries**
143+
144+
COPY queries allow you to use the [PostgreSQL COPY command](https://www.postgresql.org/docs/10/static/sql-copy.html) for efficient streaming of data to and from CARTO.
145+
146+
Here is a basic example of its usage:
147+
148+
```python
149+
from carto.sql import SQLClient
150+
from carto.sql import CopySQLClient
151+
152+
sql_client = SQLClient(auth_client)
153+
copy_client = CopySQLClient(auth_client)
154+
155+
# Create a destination table for the copy with the right schema
156+
sql_client.send("""
157+
CREATE TABLE IF NOT EXISTS copy_example (
158+
the_geom geometry(Geometry,4326),
159+
name text,
160+
age integer
161+
)
162+
""")
163+
sql_client.send("SELECT CDB_CartodbfyTable(current_schema, 'copy_example')")
164+
165+
# COPY FROM a csv file in the filesytem
166+
from_query = 'COPY copy_example (the_geom, name, age) FROM stdin WITH (FORMAT csv, HEADER true)'
167+
result = copy_client.copyfrom_file_path(from_query, 'copy_from.csv')
168+
169+
# COPY TO a file in the filesystem
170+
to_query = 'COPY copy_example TO stdout WITH (FORMAT csv, HEADER true)'
171+
copy_client.copyto_file_path(to_query, 'export.csv')
172+
```
173+
174+
Here's an equivalent, more pythonic example of the COPY FROM, using a `file` object:
175+
176+
```python
177+
with open('copy_from.csv', 'rb') as f:
178+
copy_client.copyfrom_file_object(from_query, f)
179+
```
180+
181+
And here is a demonstration of how to generate and stream data directly (no need for a file at all):
182+
183+
```python
184+
def rows():
185+
# note the \n to delimit rows
186+
yield bytearray(u'the_geom,name,age\n', 'utf-8')
187+
for i in range(1,80):
188+
row = u'SRID=4326;POINT({lon} {lat}),{name},{age}\n'.format(
189+
lon = i,
190+
lat = i,
191+
name = 'fulano',
192+
age = 100 - i
193+
)
194+
yield bytearray(row, 'utf-8')
195+
copy_client.copyfrom(from_query, rows())
196+
```
197+
142198
For more examples on how to use the SQL API, please refer to the **examples** folder or the API docs.
143199

144200
Import API

carto/auth.py

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import re
1818
import sys
1919
import warnings
20+
import pkg_resources
2021

2122
from pyrestcli.auth import BaseAuthClient, BasicAuthClient
2223

@@ -64,7 +65,23 @@ def check_base_url(self, base_url):
6465
return base_url
6566

6667

67-
class APIKeyAuthClient(_UsernameGetter, _BaseUrlChecker, BaseAuthClient):
68+
class _ClientIdentifier:
69+
70+
CARTO_VERSION = pkg_resources.require('carto')[0].version
71+
72+
def get_user_agent(self, name='carto-python-sdk'):
73+
return "{name}/{version}".format(
74+
name=name,
75+
version=self.CARTO_VERSION)
76+
77+
def get_client_identifier(self, prefix='cps'):
78+
return "{prefix}-{version}".format(
79+
prefix=prefix,
80+
version=self.CARTO_VERSION)
81+
82+
83+
class APIKeyAuthClient(_UsernameGetter, _BaseUrlChecker, _ClientIdentifier,
84+
BaseAuthClient):
6885
"""
6986
This class provides you with authenticated access to CARTO's APIs using
7087
your API key.
@@ -90,6 +107,8 @@ def __init__(self, base_url, api_key, organization=None, session=None):
90107
self.api_key = api_key
91108
base_url = self.check_base_url(base_url)
92109
self.username = self.get_user_name(base_url)
110+
self.user_agent = self.get_user_agent()
111+
self.client_id = self.get_client_identifier()
93112

94113
super(APIKeyAuthClient, self).__init__(base_url, session=session)
95114

@@ -120,12 +139,20 @@ def send(self, relative_path, http_method, **requests_args):
120139

121140
def prepare_send(self, http_method, **requests_args):
122141
http_method = http_method.lower()
142+
params = {
143+
"api_key": self.api_key,
144+
"client": self.client_id
145+
}
123146
if (http_method in ['post', 'put']) and "json" in requests_args:
124-
requests_args["json"].update({"api_key": self.api_key})
147+
requests_args["json"].update(params)
125148
else:
126149
if "params" not in requests_args:
127150
requests_args["params"] = {}
128-
requests_args["params"].update({"api_key": self.api_key})
151+
requests_args["params"].update(params)
152+
153+
if not requests_args.get('headers', None):
154+
requests_args['headers'] = {}
155+
requests_args['headers'].update({'User-Agent': self.user_agent})
129156

130157
return http_method, requests_args
131158

0 commit comments

Comments
 (0)