stale[bot] closed pull request #2228: [AIRFLOW-351] Ensure python_callable is pickleable URL: https://github.com/apache/incubator-airflow/pull/2228
This is a PR merged from a forked repository. As GitHub hides the original diff on merge, it is displayed below for the sake of provenance: As this is a foreign pull request (from a fork), the diff is supplied below (as it won't show otherwise due to GitHub magic): diff --git a/airflow/operators/python_operator.py b/airflow/operators/python_operator.py index cf240f2802..a8febf0d74 100644 --- a/airflow/operators/python_operator.py +++ b/airflow/operators/python_operator.py @@ -13,8 +13,10 @@ # limitations under the License. from builtins import str +import copy from datetime import datetime import logging +import pickle from airflow.exceptions import AirflowException from airflow.models import BaseOperator, TaskInstance @@ -66,6 +68,10 @@ def __init__( super(PythonOperator, self).__init__(*args, **kwargs) if not callable(python_callable): raise AirflowException('`python_callable` param must be callable') + try: + pickle.dumps(copy.deepcopy(python_callable)) + except TypeError: + raise AirflowException('`python_callable` param must be pickleable') self.python_callable = python_callable self.op_args = op_args or [] self.op_kwargs = op_kwargs or {} diff --git a/tests/operators/python_operator.py b/tests/operators/python_operator.py index 3aa8b6cf8f..56053052f6 100644 --- a/tests/operators/python_operator.py +++ b/tests/operators/python_operator.py @@ -15,6 +15,7 @@ from __future__ import print_function, unicode_literals import datetime +import threading import unittest from airflow import configuration, DAG @@ -33,6 +34,28 @@ FROZEN_NOW = datetime.datetime(2016, 1, 2, 12, 1, 1) +class NotPickleable(object): + + def __init__(self): + # [AIRFLOW-351] This object prevents pickle, which + # eventually causes TypeError by running `airflow clear` + self.lock = threading.Lock() + + def callable(self): + pass + + +class StatefulTask(object): + def do_run(self): + self.run = True + + def clear_run(self): + self.run = False + + def is_run(self): + return self.run + + class PythonOperatorTest(unittest.TestCase): def setUp(self): @@ -45,27 +68,19 @@ def setUp(self): 'start_date': DEFAULT_DATE}, schedule_interval=INTERVAL) self.addCleanup(self.dag.clear) - self.clear_run() - self.addCleanup(self.clear_run) - - def do_run(self): - self.run = True - - def clear_run(self): - self.run = False - - def is_run(self): - return self.run + self.stateful_task = StatefulTask() + self.stateful_task.clear_run() + self.addCleanup(self.stateful_task.clear_run) def test_python_operator_run(self): """Tests that the python callable is invoked on task run.""" task = PythonOperator( - python_callable=self.do_run, + python_callable=self.stateful_task.do_run, task_id='python_operator', dag=self.dag) - self.assertFalse(self.is_run()) + self.assertFalse(self.stateful_task.is_run()) task.run(start_date=DEFAULT_DATE, end_date=DEFAULT_DATE) - self.assertTrue(self.is_run()) + self.assertTrue(self.stateful_task.is_run()) def test_python_operator_python_callable_is_callable(self): """Tests that PythonOperator will only instantiate if @@ -83,6 +98,15 @@ def test_python_operator_python_callable_is_callable(self): task_id='python_operator', dag=self.dag) + def test_python_operator_python_callable_is_pickleable(self): + """Tests that PythonOperator will only instantiate if + the python_callable argument is able to pickle.""" + with self.assertRaises(AirflowException): + PythonOperator( + python_callable=NotPickleable().callable, + task_id='python_operator', + dag=self.dag) + class BranchOperatorTest(unittest.TestCase): def setUp(self): ---------------------------------------------------------------- This is an automated message from the Apache Git Service. To respond to the message, please log on GitHub and use the URL above to go to the specific comment. For queries about this service, please contact Infrastructure at: us...@infra.apache.org With regards, Apache Git Services