Merge lp:~allenap/maas/fix-mutable-argument-defaults into lp:~maas-committers/maas/trunk

Proposed by Gavin Panella
Status: Merged
Approved by: Gavin Panella
Approved revision: no longer in the source branch.
Merged at revision: 5874
Proposed branch: lp:~allenap/maas/fix-mutable-argument-defaults
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 218 lines (+78/-70)
4 files modified
src/maasserver/forms/ephemeral.py (+2/-1)
src/provisioningserver/drivers/pod/__init__.py (+9/-7)
src/provisioningserver/drivers/storage/__init__.py (+3/-3)
src/provisioningserver/logger/testing/logsomething.py (+64/-59)
To merge this branch: bzr merge lp:~allenap/maas/fix-mutable-argument-defaults
Reviewer Review Type Date Requested Status
Mike Pontillo (community) Approve
Review via email: mp+321300@code.launchpad.net

Commit message

Replace mutable arguments.

Description of the change

Mutable arguments are generally a bad thing. One pattern in particular
has appeared in MAAS; here's a reduced example:

  @attr.s
  class DiscoveredMachineInterface(AttrHelperMixin):
      tags = attr.ib(convert=convert_list(str), default=[])

The problem here is that every new instance will share the same initial
list of tags.

  >>> dmi = DiscoveredMachineInterface("mac")
  >>> dmi.tags.append("foo")
  >>> dmi
  DiscoveredMachineInterface(
      mac_address='mac', vid=-1, tags=['foo'], boot=False)

  >>> dmi2 = DiscoveredMachineInterface("mac2")
  >>> dmi2
  DiscoveredMachineInterface(
      mac_address='mac2', vid=-1, tags=['foo'], boot=False)

The fix here is to use a default *factory*:

  tags = attr.ib(convert=convert_list(str), default=attr.Factory(list))

I discovered these problems using findmutargs.py from:

  lp:~allenap/+junk/find-bad-argument-defaults

The change in this branch to `logsomething.py` was made entirely to
allow `findmutargs.py` to load it without setting it off.

To post a comment you must log in.
Revision history for this message
Mike Pontillo (mpontillo) wrote :

Looks good.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/maasserver/forms/ephemeral.py'
2--- src/maasserver/forms/ephemeral.py 2017-03-02 09:41:37 +0000
3+++ src/maasserver/forms/ephemeral.py 2017-03-29 14:02:03 +0000
4@@ -46,7 +46,8 @@
5 self.fields['testing_scripts'] = MultipleChoiceField(
6 required=False, initial=None, choices=choices)
7
8- def __init__(self, instance, user, data={}, **kwargs):
9+ def __init__(self, instance, user, data=None, **kwargs):
10+ data = {} if data is None else data
11 super().__init__(data=data, **kwargs)
12 self._set_up_script_fields()
13 self.instance = instance
14
15=== modified file 'src/provisioningserver/drivers/pod/__init__.py'
16--- src/provisioningserver/drivers/pod/__init__.py 2017-03-23 18:20:00 +0000
17+++ src/provisioningserver/drivers/pod/__init__.py 2017-03-29 14:02:03 +0000
18@@ -129,7 +129,7 @@
19 """Discovered machine interface."""
20 mac_address = attr.ib(convert=str)
21 vid = attr.ib(convert=int, default=-1)
22- tags = attr.ib(convert=convert_list(str), default=[])
23+ tags = attr.ib(convert=convert_list(str), default=attr.Factory(list))
24 boot = attr.ib(convert=bool, default=False)
25
26
27@@ -140,7 +140,7 @@
28 serial = attr.ib(convert=convert_obj(str, optional=True))
29 size = attr.ib(convert=int)
30 block_size = attr.ib(convert=int, default=512)
31- tags = attr.ib(convert=convert_list(str), default=[])
32+ tags = attr.ib(convert=convert_list(str), default=attr.Factory(list))
33 id_path = attr.ib(convert=convert_obj(str, optional=True), default=None)
34
35
36@@ -155,8 +155,9 @@
37 block_devices = attr.ib(
38 convert=convert_list(DiscoveredMachineBlockDevice))
39 power_state = attr.ib(convert=str, default='unknown')
40- power_parameters = attr.ib(convert=convert_obj(dict), default={})
41- tags = attr.ib(convert=convert_list(str), default=[])
42+ power_parameters = attr.ib(
43+ convert=convert_obj(dict), default=attr.Factory(dict))
44+ tags = attr.ib(convert=convert_list(str), default=attr.Factory(list))
45
46
47 @attr.s
48@@ -184,11 +185,12 @@
49 hints = attr.ib(convert=convert_obj(DiscoveredPodHints))
50 local_disks = attr.ib(convert=int, default=-1)
51 capabilities = attr.ib(
52- convert=convert_list(str), default=[Capabilities.FIXED_LOCAL_STORAGE])
53+ convert=convert_list(str), default=attr.Factory(
54+ lambda: [Capabilities.FIXED_LOCAL_STORAGE]))
55 machines = attr.ib(
56- convert=convert_list(DiscoveredMachine), default=[])
57+ convert=convert_list(DiscoveredMachine), default=attr.Factory(list))
58 storage = attr.ib(
59- convert=convert_list(DiscoveredStorage), default=[])
60+ convert=convert_list(DiscoveredStorage), default=attr.Factory(list))
61
62
63 @attr.s
64
65=== modified file 'src/provisioningserver/drivers/storage/__init__.py'
66--- src/provisioningserver/drivers/storage/__init__.py 2017-03-22 20:13:59 +0000
67+++ src/provisioningserver/drivers/storage/__init__.py 2017-03-29 14:02:03 +0000
68@@ -104,7 +104,7 @@
69 """Discovered storage volume."""
70 size = attr.ib(convert=int)
71 block_size = attr.ib(convert=int, default=512)
72- tags = attr.ib(convert=convert_list(str), default=[])
73+ tags = attr.ib(convert=convert_list(str), default=attr.Factory(list))
74
75
76 @attr.s
77@@ -112,8 +112,8 @@
78 """Discovered storage information."""
79 size = attr.ib(convert=int)
80 volumes = attr.ib(
81- convert=convert_list(DiscoveredVolume), default=[])
82- parameters = attr.ib(convert=convert_obj(dict), default={})
83+ convert=convert_list(DiscoveredVolume), default=attr.Factory(list))
84+ parameters = attr.ib(convert=convert_obj(dict), default=attr.Factory(dict))
85
86 # When a Pod discovers storage it sets the driver_type so MAAS knows
87 # which storage driver to call to perform the action. When a storage
88
89=== modified file 'src/provisioningserver/logger/testing/logsomething.py'
90--- src/provisioningserver/logger/testing/logsomething.py 2016-12-07 12:46:14 +0000
91+++ src/provisioningserver/logger/testing/logsomething.py 2017-03-29 14:02:03 +0000
92@@ -27,62 +27,67 @@
93 import twisted.python.log
94
95
96-modes = provisioningserver.logger.LoggingMode
97-
98-parser = argparse.ArgumentParser()
99-parser.add_argument("--name", required=True)
100-parser.add_argument("--verbosity", type=int, required=True)
101-# Resets the verbosity at runtime (after initially setting it).
102-parser.add_argument("--set-verbosity", type=int, required=False)
103-parser.add_argument(
104- "--mode", type=modes.__getitem__, help=" or ".join(
105- mode.name for mode in modes))
106-options = parser.parse_args()
107-
108-# Configure logging. This is the main entry-point.
109-provisioningserver.logger.configure(
110- verbosity=options.verbosity, mode=options.mode)
111-
112-if options.set_verbosity is not None:
113- provisioningserver.logger.set_verbosity(options.set_verbosity)
114-
115-# Simulate what `twistd` does when passed `--logger=...` in Twisted 16.0. In
116-# 16.4 something similar happens, but globalLogBeginner.beginLoggingTo() is
117-# called without going via `twisted.python.log`.
118-if options.mode == modes.TWISTD:
119- emitter = provisioningserver.logger.EventLogger()
120- twisted.python.log.startLoggingWithObserver(emitter)
121-
122-# Twisted, new.
123-twisted.logger.Logger(options.name).debug("From `twisted.logger`.")
124-twisted.logger.Logger(options.name).info("From `twisted.logger`.")
125-twisted.logger.Logger(options.name).warn("From `twisted.logger`.")
126-twisted.logger.Logger(options.name).error("From `twisted.logger`.")
127-
128-# Twisted, legacy.
129-twisted.python.log.msg("From `twisted.python.log`.", system=options.name)
130-
131-# Standard library.
132-logging.getLogger(options.name).debug("From `logging`.")
133-logging.getLogger(options.name).info("From `logging`.")
134-logging.getLogger(options.name).warning("From `logging`.")
135-logging.getLogger(options.name).error("From `logging`.")
136-
137-# Standard library, "maas" logger.
138-maaslog = provisioningserver.logger.get_maas_logger(options.name)
139-maaslog.debug("From `get_maas_logger`.")
140-maaslog.info("From `get_maas_logger`.")
141-maaslog.warning("From `get_maas_logger`.")
142-maaslog.error("From `get_maas_logger`.")
143-
144-# Standard IO.
145-print("Printing to stdout.", file=sys.stdout, flush=True)
146-print("Printing to stderr.", file=sys.stderr, flush=True)
147-
148-# Warnings.
149-warnings.formatwarning = lambda message, *_, **__: str(message)
150-warnings.warn("This is a warning!")
151-
152-# Make sure everything is flushed.
153-sys.stdout.flush()
154-sys.stderr.flush()
155+def main():
156+ modes = provisioningserver.logger.LoggingMode
157+
158+ parser = argparse.ArgumentParser()
159+ parser.add_argument("--name", required=True)
160+ parser.add_argument("--verbosity", type=int, required=True)
161+ # Resets the verbosity at runtime (after initially setting it).
162+ parser.add_argument("--set-verbosity", type=int, required=False)
163+ parser.add_argument(
164+ "--mode", type=modes.__getitem__, help=" or ".join(
165+ mode.name for mode in modes))
166+ options = parser.parse_args()
167+
168+ # Configure logging. This is the main entry-point.
169+ provisioningserver.logger.configure(
170+ verbosity=options.verbosity, mode=options.mode)
171+
172+ if options.set_verbosity is not None:
173+ provisioningserver.logger.set_verbosity(options.set_verbosity)
174+
175+ # Simulate what `twistd` does when passed `--logger=...` in Twisted 16.0.
176+ # In 16.4 a similar thing happens, but globalLogBeginner.beginLoggingTo()
177+ # is called without going via `twisted.python.log`.
178+ if options.mode == modes.TWISTD:
179+ emitter = provisioningserver.logger.EventLogger()
180+ twisted.python.log.startLoggingWithObserver(emitter)
181+
182+ # Twisted, new.
183+ twisted.logger.Logger(options.name).debug("From `twisted.logger`.")
184+ twisted.logger.Logger(options.name).info("From `twisted.logger`.")
185+ twisted.logger.Logger(options.name).warn("From `twisted.logger`.")
186+ twisted.logger.Logger(options.name).error("From `twisted.logger`.")
187+
188+ # Twisted, legacy.
189+ twisted.python.log.msg("From `twisted.python.log`.", system=options.name)
190+
191+ # Standard library.
192+ logging.getLogger(options.name).debug("From `logging`.")
193+ logging.getLogger(options.name).info("From `logging`.")
194+ logging.getLogger(options.name).warning("From `logging`.")
195+ logging.getLogger(options.name).error("From `logging`.")
196+
197+ # Standard library, "maas" logger.
198+ maaslog = provisioningserver.logger.get_maas_logger(options.name)
199+ maaslog.debug("From `get_maas_logger`.")
200+ maaslog.info("From `get_maas_logger`.")
201+ maaslog.warning("From `get_maas_logger`.")
202+ maaslog.error("From `get_maas_logger`.")
203+
204+ # Standard IO.
205+ print("Printing to stdout.", file=sys.stdout, flush=True)
206+ print("Printing to stderr.", file=sys.stderr, flush=True)
207+
208+ # Warnings.
209+ warnings.formatwarning = lambda message, *_, **__: str(message)
210+ warnings.warn("This is a warning!")
211+
212+ # Make sure everything is flushed.
213+ sys.stdout.flush()
214+ sys.stderr.flush()
215+
216+
217+if __name__ == '__main__':
218+ main()