Skip to content

Commit 10ee622

Browse files
jlowinbolkedebruin
authored andcommitted
Deprecate *args and **kwargs in BaseOperator
BaseOperator silently accepts any arguments. This deprecates the behavior with a warning that says it will be forbidden in Airflow 2.0. This PR also turns on DeprecationWarnings by default, which in turn revealed that inspect.getargspec is deprecated. Here it is replaced by `inspect.signature` (Python 3) or `funcsigs.signature` (Python 2). Lastly, this brought to attention that example_http_operator was passing an illegal argument.
1 parent 86f3463 commit 10ee622

File tree

7 files changed

+66
-13
lines changed

7 files changed

+66
-13
lines changed

UPDATING.md

+14-5
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,24 @@
11
# Updating Airflow
22

3-
This file aims to document the backwards-incompatible changes in Airflow and
4-
assist people with migrating to a new version.
3+
This file documents any backwards-incompatible changes in Airflow and
4+
assists people when migrating to a new version.
55

6-
## 1.7 to 1.8
76

8-
### DAGs now don't start automatically when created
7+
## Airflow 1.8
98

10-
To retain the old behavior, add this to your configuration:
9+
### Changes to Behavior
10+
11+
#### New DAGs are paused by default
12+
13+
Previously, new DAGs would be scheduled immediately. To retain the old behavior, add this to airflow.cfg:
1114

1215
```
16+
[core]
1317
dags_are_paused_at_creation = False
1418
```
1519

20+
### Deprecated Features
21+
These features are marked for deprecation. They may still work (and raise a `DeprecationWarning`), but are no longer supported and will be removed entirely in Airflow 2.0
22+
23+
#### Operators no longer accept arbitrary arguments
24+
Previously, `Operator.__init__()` accepted any arguments (either positional `*args` or keyword `**kwargs`) without complaint. Now, invalid arguments will be rejected.

airflow/configuration.py

+4
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import logging
99
import os
1010
import subprocess
11+
import warnings
1112

1213
from future import standard_library
1314
standard_library.install_aliases()
@@ -16,6 +17,9 @@
1617
from collections import OrderedDict
1718
from configparser import ConfigParser
1819

20+
# show DeprecationWarning and PendingDeprecationWarning
21+
warnings.simplefilter('default', DeprecationWarning)
22+
warnings.simplefilter('default', PendingDeprecationWarning)
1923

2024
class AirflowConfigException(Exception):
2125
pass

airflow/example_dags/example_http_operator.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@
6666

6767
sensor = HttpSensor(
6868
task_id='http_sensor_check',
69-
conn_id='http_default',
69+
http_conn_id='http_default',
7070
endpoint='',
7171
params={},
7272
response_check=lambda response: True if "Google" in response.content else False,

airflow/models.py

+12
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
import socket
3838
import sys
3939
import traceback
40+
import warnings
4041
from urllib.parse import urlparse
4142

4243
from sqlalchemy import (
@@ -1597,6 +1598,17 @@ def __init__(
15971598
*args,
15981599
**kwargs):
15991600

1601+
if args or kwargs:
1602+
# TODO remove *args and **kwargs in Airflow 2.0
1603+
warnings.warn(
1604+
'Invalid arguments were passed to {c}. Support for '
1605+
'passing such arguments will be dropped in Airflow 2.0.'
1606+
'Invalid arguments were:'
1607+
'\n*args: {a}\n**kwargs: {k}'.format(
1608+
c=self.__class__.__name__, a=args, k=kwargs),
1609+
category=PendingDeprecationWarning
1610+
)
1611+
16001612
validate_key(task_id)
16011613
self.dag_id = dag.dag_id if dag else 'adhoc_' + owner
16021614
self.task_id = task_id

airflow/utils/decorators.py

+17-7
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,17 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414
#
15-
import inspect
1615
import os
1716

17+
# inspect.signature is only available in Python 3. funcsigs.signature is
18+
# a backport.
19+
try:
20+
import inspect
21+
signature = inspect.signature
22+
except AttributeError:
23+
import funcsigs
24+
signature = funcsigs.signature
25+
1826
from copy import copy
1927
from functools import wraps
2028

@@ -57,12 +65,14 @@ def wrapper(*args, **kwargs):
5765

5866
dag_args.update(default_args)
5967
default_args = dag_args
60-
arg_spec = inspect.getargspec(func)
61-
num_defaults = len(arg_spec.defaults) if arg_spec.defaults else 0
62-
non_optional_args = arg_spec.args[:-num_defaults]
63-
if 'self' in non_optional_args:
64-
non_optional_args.remove('self')
65-
for arg in func.__code__.co_varnames:
68+
69+
sig = signature(func)
70+
non_optional_args = [
71+
name for (name, param) in sig.parameters.items()
72+
if param.default == param.empty and
73+
param.name != 'self' and
74+
param.kind not in (param.VAR_POSITIONAL, param.VAR_KEYWORD)]
75+
for arg in sig.parameters:
6676
if arg in default_args and arg not in kwargs:
6777
kwargs[arg] = default_args[arg]
6878
missing_args = list(set(non_optional_args) - set(kwargs))

setup.py

+1
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ def run(self):
122122
'flask-cache>=0.13.1, <0.14',
123123
'flask-login==0.2.11',
124124
'future>=0.15.0, <0.16',
125+
'funcsigs>=0.4, <1'
125126
'gunicorn>=19.3.0, <19.4.0', # 19.4.? seemed to have issues
126127
'jinja2>=2.7.3, <3.0',
127128
'markdown>=2.5.2, <3.0',

tests/core.py

+17
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from email.mime.application import MIMEApplication
1313
import signal
1414
from time import sleep
15+
import warnings
1516

1617
from dateutil.relativedelta import relativedelta
1718

@@ -320,6 +321,22 @@ def test_clear_api(self):
320321
ti = models.TaskInstance(task=task, execution_date=DEFAULT_DATE)
321322
ti.are_dependents_done()
322323

324+
def test_illegal_args(self):
325+
"""
326+
Tests that Operators reject illegal arguments
327+
"""
328+
with warnings.catch_warnings(record=True) as w:
329+
t = operators.BashOperator(
330+
task_id='test_illegal_args',
331+
bash_command='echo success',
332+
dag=self.dag,
333+
illegal_argument_1234='hello?')
334+
self.assertTrue(
335+
issubclass(w[0].category, PendingDeprecationWarning))
336+
self.assertIn(
337+
'Invalid arguments were passed to BashOperator.',
338+
w[0].message.args[0])
339+
323340
def test_bash_operator(self):
324341
t = operators.BashOperator(
325342
task_id='time_sensor_check',

0 commit comments

Comments
 (0)