Skip to content
This repository was archived by the owner on Sep 3, 2022. It is now read-only.

Add Test Cases for Python 3.7 #720

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
Open
5 changes: 4 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ matrix:
env: TOX_ENV=py27
- python: 3.5
env: TOX_ENV=py35
- python: 3.7
env: TOX_ENV=py37
dist: xenial
- python: 2.7
env: TOX_ENV=flake8
- python: 2.7
Expand All @@ -18,7 +21,7 @@ before_install:
- tsc --module amd --noImplicitAny --outdir datalab/notebook/static datalab/notebook/static/*.ts
# We use tox for actually running tests.
- pip install --upgrade pip tox

script:
# tox reads its configuration from tox.ini.
- tox -e $TOX_ENV
Expand Down
4 changes: 2 additions & 2 deletions datalab/utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

"""Google Cloud Platform library - Internal Helpers."""

from ._async import async, async_function, async_method
from ._async import async_, async_function, async_method
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for incorporating the naming style from #728. It is shorter and a more standard pattern.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks you very much for your support.

from ._gcp_job import GCPJob
from ._http import Http, RequestException
from ._iterator import Iterator
Expand All @@ -24,7 +24,7 @@
from ._utils import print_exception_with_last_stack, get_item, compare_datetimes, \
pick_unused_port, is_http_running_on, gcs_copy_file

__all__ = ['async', 'async_function', 'async_method', 'GCPJob', 'Http', 'RequestException',
__all__ = ['async_', 'async_function', 'async_method', 'GCPJob', 'Http', 'RequestException',
'Iterator', 'Job', 'JobError', 'JSONEncoder', 'LRUCache', 'LambdaJob', 'DataflowJob',
'print_exception_with_last_stack', 'get_item', 'compare_datetimes', 'pick_unused_port',
'is_http_running_on', 'gcs_copy_file']
12 changes: 6 additions & 6 deletions datalab/utils/_async.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
from future.utils import with_metaclass


class async(with_metaclass(abc.ABCMeta, object)):
class async_(with_metaclass(abc.ABCMeta, object)):
""" Base class for async_function/async_method. Creates a wrapped function/method that will
run the original function/method on a thread pool worker thread and return a Job instance
for monitoring the status of the thread.
Expand Down Expand Up @@ -55,27 +55,27 @@ def __call__(self, *args, **kwargs):
return _job.Job(future=self.executor.submit(self._call, *args, **kwargs))


class async_function(async):
class async_function(async_):
""" This decorator can be applied to any static function that makes blocking calls to create
a modified version that creates a Job and returns immediately; the original
method will be called on a thread pool worker thread.
"""

def _call(self, *args, **kwargs):
# Call the wrapped method.
return self._function(*async._preprocess_args(*args), **async._preprocess_kwargs(**kwargs))
return self._function(*async_._preprocess_args(*args), **async_._preprocess_kwargs(**kwargs))


class async_method(async):
class async_method(async_):
""" This decorator can be applied to any class instance method that makes blocking calls to create
a modified version that creates a Job and returns immediately; the original method will be
called on a thread pool worker thread.
"""

def _call(self, *args, **kwargs):
# Call the wrapped method.
return self._function(self.obj, *async._preprocess_args(*args),
**async._preprocess_kwargs(**kwargs))
return self._function(self.obj, *async_._preprocess_args(*args),
**async_._preprocess_kwargs(**kwargs))

def __get__(self, instance, owner):
# This is important for attribute inheritance and setting self.obj so it can be
Expand Down
2 changes: 1 addition & 1 deletion datalab/utils/_lambda_job.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def __init__(self, fn, job_id, *args, **kwargs):
job_id: an optional ID for the job. If None, a UUID will be generated.
"""
super(LambdaJob, self).__init__(job_id)
self._future = _async.async.executor.submit(fn, *args, **kwargs)
self._future = _async.async_.executor.submit(fn, *args, **kwargs)

def __repr__(self):
"""Returns a representation for the job for showing in the notebook.
Expand Down
3 changes: 3 additions & 0 deletions datalab/utils/commands/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import pandas_profiling
except ImportError:
pass
import six
import sys
import types
import yaml
Expand Down Expand Up @@ -323,6 +324,8 @@ def parse_config(config, env, as_dict=True):
config = {}
elif stripped[0] == '{':
config = json.loads(config)
elif six.PY3:
config = yaml.load(config, Loader=yaml.FullLoader)
else:
config = yaml.load(config)
if as_dict:
Expand Down
4 changes: 2 additions & 2 deletions google/datalab/utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

"""Google Cloud Platform library - Internal Helpers."""

from ._async import async, async_function, async_method
from ._async import async_, async_function, async_method
from ._http import Http, RequestException
from ._iterator import Iterator
from ._json_encoder import JSONEncoder
Expand All @@ -23,7 +23,7 @@
pick_unused_port, is_http_running_on, gcs_copy_file, python_portable_string


__all__ = ['async', 'async_function', 'async_method', 'Http', 'RequestException', 'Iterator',
__all__ = ['async_', 'async_function', 'async_method', 'Http', 'RequestException', 'Iterator',
'JSONEncoder', 'LRUCache', 'LambdaJob', 'DataflowJob',
'print_exception_with_last_stack', 'get_item', 'compare_datetimes', 'pick_unused_port',
'is_http_running_on', 'gcs_copy_file', 'python_portable_string']
12 changes: 6 additions & 6 deletions google/datalab/utils/_async.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
from future.utils import with_metaclass


class async(with_metaclass(abc.ABCMeta, object)):
class async_(with_metaclass(abc.ABCMeta, object)):
""" Base class for async_function/async_method. Creates a wrapped function/method that will
run the original function/method on a thread pool worker thread and return a Job instance
for monitoring the status of the thread.
Expand Down Expand Up @@ -55,27 +55,27 @@ def __call__(self, *args, **kwargs):
return Job(future=self.executor.submit(self._call, *args, **kwargs))


class async_function(async):
class async_function(async_):
""" This decorator can be applied to any static function that makes blocking calls to create
a modified version that creates a Job and returns immediately; the original
method will be called on a thread pool worker thread.
"""

def _call(self, *args, **kwargs):
# Call the wrapped method.
return self._function(*async._preprocess_args(*args), **async._preprocess_kwargs(**kwargs))
return self._function(*async_._preprocess_args(*args), **async_._preprocess_kwargs(**kwargs))


class async_method(async):
class async_method(async_):
""" This decorator can be applied to any class instance method that makes blocking calls to create
a modified version that creates a Job and returns immediately; the original method will be
called on a thread pool worker thread.
"""

def _call(self, *args, **kwargs):
# Call the wrapped method.
return self._function(self.obj, *async._preprocess_args(*args),
**async._preprocess_kwargs(**kwargs))
return self._function(self.obj, *async_._preprocess_args(*args),
**async_._preprocess_kwargs(**kwargs))

def __get__(self, instance, owner):
# This is important for attribute inheritance and setting self.obj so it can be
Expand Down
2 changes: 1 addition & 1 deletion google/datalab/utils/_lambda_job.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def __init__(self, fn, job_id, *args, **kwargs):
job_id: an optional ID for the job. If None, a UUID will be generated.
"""
super(LambdaJob, self).__init__(job_id)
self._future = _async.async.executor.submit(fn, *args, **kwargs)
self._future = _async.async_.executor.submit(fn, *args, **kwargs)

def __repr__(self):
"""Returns a representation for the job for showing in the notebook.
Expand Down
5 changes: 5 additions & 0 deletions google/datalab/utils/commands/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import pandas_profiling
except ImportError:
pass
import six
import sys
import yaml

Expand Down Expand Up @@ -328,6 +329,8 @@ def parse_config(config, env, as_dict=True):
config = {}
elif stripped[0] == '{':
config = json.loads(config)
elif six.PY3:
config = yaml.load(config, Loader=yaml.FullLoader)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What are these loader changes addressing? This seems unrelated to Python 3.7 compat to me at first glance.

Copy link
Author

@seii-saintway seii-saintway Mar 12, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am sorry that I cannot remember the reason clearly.
But I searched on google and found an issue concerning pyyaml version.

I remember that I built the datalab image with py37 and got into some troubles with pyyaml before.

As the pyyaml version show as below,

'pyyaml>=3.11',

I guess that pyyaml will be installed as different version in py2 and py3.

As you could see in the py35 coverage test, there were warnings concerning YAMLLoadWarning.

/home/travis/build/googledatalab/pydatalab/tests/pipeline/pipeline_tests.py:305: YAMLLoadWarning: calling yaml.load() without Loader=... is deprecated, as the default Loader is unsafe. Please read https://msg.pyyaml.org/load for full details.
  dag_dict = yaml.load(PipelineTest._test_pipeline_yaml_spec)
/home/travis/build/googledatalab/pydatalab/tests/pipeline/pipeline_tests.py:265: YAMLLoadWarning: calling yaml.load() without Loader=... is deprecated, as the default Loader is unsafe. Please read https://msg.pyyaml.org/load for full details.
  dag_dict = yaml.load(PipelineTest._test_pipeline_yaml_spec)
/home/travis/build/googledatalab/pydatalab/tests/pipeline/pipeline_tests.py:280: YAMLLoadWarning: calling yaml.load() without Loader=... is deprecated, as the default Loader is unsafe. Please read https://msg.pyyaml.org/load for full details.
  dag_dict = yaml.load(PipelineTest._test_pipeline_yaml_spec)

Maybe there will be warnings or errors in py37, too.
I'm sorry that maybe it is better to open a new PR concerning the YAMLLoadWarning thing.
And it is better add some py37 coverage tests.

else:
config = yaml.load(config)
if as_dict:
Expand Down Expand Up @@ -373,6 +376,8 @@ def parse_config_for_selected_keys(content, keys):
return {}, None
elif stripped[0] == '{':
config = json.loads(content)
elif six.PY3:
config = yaml.load(content, Loader=yaml.FullLoader)
else:
config = yaml.load(content)

Expand Down
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@
classifiers=[
"Programming Language :: Python",
"Programming Language :: Python :: 2",
"Programming Language :: Python :: 3",
"Development Status :: 4 - Beta",
"Environment :: Other Environment",
"Intended Audience :: Developers",
Expand Down
13 changes: 11 additions & 2 deletions tests/_util/commands_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,13 @@ def test_subcommand_line_cell(self):
'string4: value4\nflag1: false')
self.assertEqual(args, {'string1': 'value1', 'string2': 'value2', 'string3': 'value3',
'command': 'subcommand2', 'flag1': False})
self.assertEqual(yaml.load(cell), {'string3': 'value3', 'string4': 'value4'})
if six.PY3:
self.assertEqual(
yaml.load(cell, Loader=yaml.FullLoader),
{'string3': 'value3', 'string4': 'value4'}
)
else:
self.assertEqual(yaml.load(cell), {'string3': 'value3', 'string4': 'value4'})

# Regular arg and cell arg cannot be the same name.
with self.assertRaises(ValueError):
Expand Down Expand Up @@ -133,7 +139,10 @@ def test_subcommand_var_replacement(self):
args, cell = parser.parse('subcommand1 --string1 $var1', 'a: b\nstring2: $var2', namespace)
self.assertEqual(args,
{'string1': 'value1', 'string2': 'value2', 'flag1': False, 'dict1': None})
self.assertEqual(yaml.load(cell), {'a': 'b', 'string2': '$var2'})
if six.PY3:
self.assertEqual(yaml.load(cell, Loader=yaml.FullLoader), {'a': 'b', 'string2': '$var2'})
else:
self.assertEqual(yaml.load(cell), {'a': 'b', 'string2': '$var2'})

cell = """
dict1:
Expand Down
16 changes: 13 additions & 3 deletions tests/pipeline/pipeline_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import datetime
import mock
import re
import six
import unittest
import yaml

Expand Down Expand Up @@ -262,7 +263,10 @@ def test_get_dag_definition(self):
'catchup=True, default_args=default_args)\n\n')

def test_get_datetime_expr(self):
dag_dict = yaml.load(PipelineTest._test_pipeline_yaml_spec)
if six.PY3:
dag_dict = yaml.load(PipelineTest._test_pipeline_yaml_spec, Loader=yaml.FullLoader)
else:
dag_dict = yaml.load(PipelineTest._test_pipeline_yaml_spec)
start = dag_dict.get('schedule').get('start')
datetime_expr = pipeline.PipelineGenerator._get_datetime_expr_str(start)

Expand All @@ -277,7 +281,10 @@ def test_get_default_args(self):
self.assertIn("'email': []", actual)
self.assertIn("'owner': 'Google Cloud Datalab'", actual)

dag_dict = yaml.load(PipelineTest._test_pipeline_yaml_spec)
if six.PY3:
dag_dict = yaml.load(PipelineTest._test_pipeline_yaml_spec, Loader=yaml.FullLoader)
else:
dag_dict = yaml.load(PipelineTest._test_pipeline_yaml_spec)
dag_dict['schedule']['retries'] = 5
dag_dict['schedule']['email_on_retry'] = False
dag_dict['schedule']['email_on_failure'] = False
Expand All @@ -302,7 +309,10 @@ def test_get_default_args(self):
self.assertIn("'max_retry_delay': timedelta(seconds=15)", actual)

def test_get_airflow_spec_with_default_schedule(self):
dag_dict = yaml.load(PipelineTest._test_pipeline_yaml_spec)
if six.PY3:
dag_dict = yaml.load(PipelineTest._test_pipeline_yaml_spec, Loader=yaml.FullLoader)
else:
dag_dict = yaml.load(PipelineTest._test_pipeline_yaml_spec)
# We delete the schedule spec to test with defaults
del dag_dict['schedule']

Expand Down
10 changes: 5 additions & 5 deletions tox.ini
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[tox]
# By default, we want to run tests for Python 2.7, Python 3.5, and run our
# flake8 checks.
envlist = py27,py35,flake8,coveralls
# By default, we want to run tests for Python 2.7, Python 3.5, Python 3.7,
# and run our flake8 checks.
envlist = py27,py35,py37,flake8,coveralls
# If an interpreter is missing locally, skip it.
skip_missing_interpreters = true

Expand All @@ -10,9 +10,9 @@ skip_missing_interpreters = true
# need them to run our tests suite.
#
# tox always installs the current package, so there's no need to list it here.
deps = apache-airflow==1.9.0
deps = apache-airflow==1.10.9
dill==0.2.6
tensorflow==1.8.0
tensorflow==1.14.0
lime==0.1.1.23
xgboost==0.6a2
# Dropping this seems to cause problems with conda in some cases.
Expand Down