Skip to content

Commit 82cadb8

Browse files
authored
[CIVP-10391] Add Python 2.7 compatibility (#73)
* COMPAT: Install mock * TST: Test on 2.7 * COMPAT: Install futures * COMPAT: Absolute imports * COMPAT: six.raise_from * COMPAT: lru_cache * COMPAT: inspect.signature * COMPAT: import from package * COMPAT: super * COMPAT: print function * COMPAT: Default kwarg after argument packing * COMPAT: Remove keyword-only argument syntax * COMPAT: ensure str * COMPAT: Remove type hints * COMPAT: FileNotFoundError * COMPAT: Ensure new-style class * COMPAT: assertCountEquals * COMPAT: StringIO and BytesIO * COMPAT: tempfile.TemporaryDirectory * COMPAT: set repr * COMPAT: BytesIO vs StringIO * COMPAT: tempfile.TemporaryDirectory * COMPAT: Mock __builtin__.open * DOC: Document 2.7 compatibility * STY: Two lines before function * STY: Ignore linting * TST: Only build docs on Python 3 * REF: Always cleanup. Use shutil. * REF: Pop kwarg * DOC: Document json_output kwarg * REF: Move FileNotFoundError to compat * COMPAT: Remove keyword argument * DEV: Protect dev dependency * MAINT: Update setuptools classifiers * DOC: Update python support * TST: Explicitly test all versions * TST: Give extra info on skips and fails * DEV: Don't build docs on python 3.6 * ENH: Don't assume fname has no path
1 parent f0223a0 commit 82cadb8

32 files changed

+227
-129
lines changed

.travis.yml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
language: python
22
python:
3+
- "2.7"
34
- "3.4"
5+
- "3.5"
6+
- "3.6"
47
install:
58
- pip install --upgrade pip setuptools
69
- pip install -r requirements.txt
@@ -11,5 +14,5 @@ env:
1114
- CIVIS_API_KEY=FOOBAR
1215
before_script: flake8 civis
1316
script:
14-
- py.test --cov civis
15-
- sphinx-build -b html -n docs/source/ docs/build/
17+
- py.test -rxs --cov civis
18+
- if [[ $TRAVIS_PYTHON_VERSION == 3.5* ]] ; then sphinx-build -b html -n docs/source/ docs/build/ ; fi

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
1010
- Added functions ``civis.io.file_id_from_run_output``, ``civis.io.file_to_dataframe``, and ``civis.io.file_to_json``.
1111
- Added ``civis.ml`` namespace with ``ModelPipeline`` interface to Civis Platform modeling capabilities.
1212
- Added ``examples`` directory with sample ``ModelPipeline`` code from ``civis.ml``.
13+
- Python 2.7 compatibility
1314

1415
### Fixed
1516
- Corrected the defaults listed in the docstring for ``civis.io.civis_to_multifile_csv``.

civis/__init__.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1-
from ._version import __version__
2-
from .civis import APIClient, find, find_one
3-
from . import io
1+
from __future__ import absolute_import
2+
3+
from civis._version import __version__
4+
from civis.civis import APIClient, find, find_one
5+
from civis import io
46

57
__all__ = ["__version__", "APIClient", "find", "find_one", "io"]

civis/base.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1+
from __future__ import absolute_import
2+
from builtins import super
13
import os
24
from posixpath import join
35
import threading
46
from concurrent import futures
7+
import six
58
import warnings
69

710
from requests.packages.urllib3.util import Retry
@@ -95,7 +98,7 @@ def is_retry(self, method, status_code, has_retry_after=False):
9598
has_retry_after=has_retry_after)
9699

97100

98-
class Endpoint:
101+
class Endpoint(object):
99102

100103
_lock = threading.Lock()
101104

@@ -119,7 +122,8 @@ def _make_request(self, method, path=None, params=None, data=None,
119122

120123
if response.status_code == 401:
121124
auth_error = response.headers["www-authenticate"]
122-
raise CivisAPIKeyError(auth_error) from CivisAPIError(response)
125+
six.raise_from(CivisAPIKeyError(auth_error),
126+
CivisAPIError(response))
123127

124128
if not response.ok:
125129
raise CivisAPIError(response)

civis/civis.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import functools
1+
from __future__ import absolute_import
22
import logging
33
import os
44

@@ -7,6 +7,7 @@
77

88
import civis
99
from civis.base import AggressiveRetry
10+
from civis.compat import lru_cache
1011
from civis.resources import generate_classes_maybe_cached
1112

1213

@@ -58,7 +59,7 @@ def find_one(object_list, filter_func=None, **kwargs):
5859

5960
class MetaMixin():
6061

61-
@functools.lru_cache()
62+
@lru_cache(maxsize=128)
6263
def get_database_id(self, database):
6364
"""Return the database ID for a given database name.
6465
@@ -86,7 +87,7 @@ def get_database_id(self, database):
8687

8788
return db["id"]
8889

89-
@functools.lru_cache()
90+
@lru_cache(maxsize=128)
9091
def get_database_credential_id(self, username, database_name):
9192
"""Return the credential ID for a given username in a given database.
9293
@@ -136,7 +137,7 @@ def get_database_credential_id(self, username, database_name):
136137

137138
return my_creds["id"]
138139

139-
@functools.lru_cache()
140+
@lru_cache(maxsize=128)
140141
def get_aws_credential_id(self, cred_name, owner=None):
141142
"""Find an AWS credential ID.
142143
@@ -202,7 +203,7 @@ def get_aws_credential_id(self, cred_name, owner=None):
202203

203204
return my_creds["id"]
204205

205-
@functools.lru_cache()
206+
@lru_cache(maxsize=128)
206207
def get_table_id(self, table, database):
207208
"""Return the table ID for a given database and table name.
208209
@@ -239,15 +240,15 @@ def get_table_id(self, table, database):
239240
return tables[0].id
240241

241242
@property
242-
@functools.lru_cache()
243+
@lru_cache(maxsize=128)
243244
def default_credential(self):
244245
"""The current user's default credential."""
245246
# NOTE: this should be optional to endpoints...so this could go away
246247
creds = self.credentials.list(default=True)
247248
return creds[0]['id'] if len(creds) > 0 else None
248249

249250
@property
250-
@functools.lru_cache()
251+
@lru_cache(maxsize=128)
251252
def username(self):
252253
"""The current user's username."""
253254
return self.users.list_me().username

civis/cli/__main__.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
which has an Apache 2.0 License:
88
https://github.com/zalando/openapi-cli-client/blob/master/LICENSE
99
"""
10+
from __future__ import print_function
1011

1112

1213
import calendar
@@ -25,6 +26,7 @@
2526
import yaml
2627
from civis.cli._cli_commands import \
2728
civis_ascii_art, files_download_cmd, files_upload_cmd
29+
from civis.compat import FileNotFoundError
2830

2931

3032
_REPLACEABLE_COMMAND_CHARS = re.compile(r'[^A-Za-z0-9]+')
@@ -130,11 +132,15 @@ def param_case_map(param_names):
130132
return result
131133

132134

133-
def invoke(method, path, op, *args, json_output=False, **kwargs):
134-
135+
def invoke(method, path, op, *args, **kwargs):
136+
"""
137+
If json_output is in `kwargs` then the output is json. Otherwise, it is
138+
yaml.
139+
"""
135140
# Remove None b/c click passes everything in as None if it's not set.
136141
kwargs = {k: v for k, v in kwargs.items()
137142
if v is not None}
143+
json_output = kwargs.pop('json_output', False)
138144

139145
# Construct the body of the request.
140146
body = {}

civis/compat.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# flake8: noqa
2+
3+
import six
4+
5+
if six.PY3:
6+
from unittest import mock
7+
from functools import lru_cache
8+
from inspect import signature
9+
FileNotFoundError = FileNotFoundError
10+
else:
11+
try:
12+
import mock
13+
except ImportError: # dev dependency
14+
pass
15+
from functools32 import lru_cache
16+
from funcsigs import signature
17+
FileNotFoundError = IOError

civis/futures.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
from __future__ import absolute_import
2+
3+
from builtins import super
4+
15
from civis import APIClient
26
from civis.base import DONE
37
from civis.polling import PollableResult

civis/io/__init__.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1-
from ._databases import query_civis, transfer_table # NOQA
2-
from ._files import * # NOQA
3-
from ._tables import * # NOQA
1+
from __future__ import absolute_import
2+
3+
from civis.io._databases import query_civis, transfer_table # NOQA
4+
from civis.io._files import * # NOQA
5+
from civis.io._tables import * # NOQA

civis/io/_databases.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from __future__ import absolute_import
2+
13
from civis import APIClient
24
from civis._utils import maybe_get_random_name
35
from civis.futures import CivisFuture

civis/io/_files.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@
44
import logging
55
import os
66
import re
7+
import six
78

89
import requests
910
from requests import HTTPError
1011

1112
from civis import APIClient, find_one
1213
from civis.base import CivisAPIError, EmptyResultError
14+
from civis.compat import FileNotFoundError
1315
from civis.utils._deprecation import deprecate_param
1416
try:
1517
from requests_toolbelt.multipart.encoder import MultipartEncoder
@@ -227,8 +229,8 @@ def file_id_from_run_output(name, job_id, run_id, regex=False, client=None):
227229
outputs = client.scripts.list_containers_runs_outputs(job_id, run_id)
228230
except CivisAPIError as err:
229231
if err.status_code == 404:
230-
raise IOError('Could not find job/run ID {}/{}'
231-
.format(job_id, run_id)) from err
232+
six.raise_from(IOError('Could not find job/run ID {}/{}'
233+
.format(job_id, run_id)), err)
232234
else:
233235
raise
234236

civis/io/_tables.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import json
22
import csv
33
import io
4+
import six
45
import warnings
56

67
from civis import APIClient
@@ -513,8 +514,11 @@ def dataframe_to_civis(df, database, table, api_key=None, client=None,
513514
if archive:
514515
warnings.warn("`archive` is deprecated and will be removed in v2.0.0. "
515516
"Use `hidden` instead.", FutureWarning)
516-
buf = io.BytesIO()
517-
txt = io.TextIOWrapper(buf, encoding='utf-8')
517+
buf = six.BytesIO()
518+
if six.PY3:
519+
txt = io.TextIOWrapper(buf, encoding='utf-8')
520+
else:
521+
txt = buf
518522
df.to_csv(txt, encoding='utf-8', index=False, **kwargs)
519523
txt.flush()
520524
buf.seek(0)

0 commit comments

Comments
 (0)