Total Complexity | 40 |
Total Lines | 200 |
Duplicated Lines | 24 % |
Coverage | 100% |
Changes | 0 |
Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like mine.models.status often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
1 | """Data structures for application/computer status.""" |
||
2 | |||
3 | 1 | import functools |
|
4 | 1 | ||
5 | import log |
||
6 | 1 | import yorm |
|
7 | |||
8 | 1 | from .timestamp import Timestamp |
|
9 | |||
10 | 1 | ||
11 | def log_running(func): |
||
12 | @functools.wraps(func) |
||
13 | 1 | def wrapped(self, application, computer): |
|
14 | running = func(self, application, computer) |
||
15 | 1 | log.debug( |
|
16 | "%s marked as %s on: %s", |
||
17 | application, |
||
18 | 1 | "started" if running else "stopped", |
|
19 | 1 | computer, |
|
20 | ) |
||
21 | 1 | return running |
|
22 | 1 | ||
23 | return wrapped |
||
24 | |||
25 | 1 | ||
26 | def log_starting(func): |
||
27 | 1 | @functools.wraps(func) |
|
28 | def wrapped(self, application, computer): |
||
29 | log.debug("Marking %s as started on %s...", application, computer) |
||
30 | 1 | result = func(self, application, computer) |
|
31 | 1 | log.debug("%s marked as started on: %s", application, computer) |
|
32 | 1 | return result |
|
33 | 1 | ||
34 | 1 | return wrapped |
|
35 | |||
36 | |||
37 | 1 | def log_stopping(func): |
|
38 | @functools.wraps(func) |
||
39 | 1 | def wrapped(self, application, computer): |
|
40 | log.debug("Marking %s as stopped on %s...", application, computer) |
||
41 | result = func(self, application, computer) |
||
42 | 1 | log.debug("%s marked as stopped on: %s", application, computer) |
|
43 | 1 | return result |
|
44 | 1 | ||
45 | 1 | return wrapped |
|
46 | 1 | ||
47 | |||
48 | @yorm.attr(computer=yorm.types.String) |
||
49 | 1 | @yorm.attr(timestamp=Timestamp) |
|
50 | 1 | class State(yorm.types.AttributeDictionary): |
|
51 | 1 | """Dictionary of computer state.""" |
|
52 | |||
53 | def __init__(self, computer=None, timestamp=None): |
||
54 | 1 | super().__init__() |
|
55 | 1 | self.computer = computer |
|
56 | 1 | self.timestamp = timestamp or Timestamp() |
|
57 | 1 | ||
58 | def __str__(self): |
||
59 | 1 | return str(self.computer) |
|
60 | 1 | ||
61 | def __lt__(self, other): |
||
62 | 1 | return str(self.computer).lower() < str(other.computer).lower() |
|
63 | 1 | ||
64 | |||
65 | @yorm.attr(all=State) |
||
66 | 1 | class StateList(yorm.types.SortedList): |
|
67 | 1 | """List of computer states for an application.""" |
|
68 | |||
69 | |||
70 | @yorm.attr(application=yorm.types.String) |
||
71 | 1 | @yorm.attr(computers=StateList) |
|
72 | 1 | @yorm.attr(next=yorm.types.NullableString) |
|
73 | 1 | class Status(yorm.types.AttributeDictionary): |
|
74 | 1 | """Dictionary of computers using an application.""" |
|
75 | |||
76 | def __init__( |
||
77 | 1 | self, application=None, computers=None, next=None |
|
78 | 1 | ): # pylint: disable=redefined-builtin |
|
79 | 1 | super().__init__() |
|
80 | 1 | self.application = application |
|
81 | 1 | self.computers = computers or StateList() |
|
82 | self.next = next |
||
83 | 1 | ||
84 | 1 | def __str__(self): |
|
85 | return str(self.application) |
||
86 | 1 | ||
87 | 1 | def __lt__(self, other): |
|
88 | return str(self.application).lower() < str(other.application).lower() |
||
89 | |||
90 | 1 | ||
91 | 1 | @yorm.attr(all=Status) |
|
92 | class StatusList(yorm.types.SortedList): |
||
93 | """List of application statuses.""" |
||
94 | |||
95 | 1 | ||
96 | 1 | @yorm.attr(applications=StatusList) |
|
97 | 1 | @yorm.attr(counter=yorm.types.Integer) |
|
98 | class ProgramStatus(yorm.types.AttributeDictionary): |
||
99 | """Dictionary of current program status.""" |
||
100 | 1 | ||
101 | 1 | def __init__(self, applications=None, counter=0): |
|
102 | 1 | super().__init__() |
|
103 | 1 | self.applications = applications or StatusList() |
|
104 | self.counter = counter |
||
105 | 1 | ||
106 | def find(self, application): |
||
107 | 1 | """Return the application status for an application.""" |
|
108 | 1 | for app_status in self.applications: |
|
109 | 1 | if app_status.application == application.name: |
|
110 | break |
||
111 | 1 | else: |
|
112 | 1 | app_status = Status(application.name) |
|
113 | 1 | self.applications.append(app_status) |
|
114 | return app_status |
||
115 | 1 | ||
116 | def get_latest(self, application): |
||
117 | 1 | """Get the last computer's name logged as running an application.""" |
|
118 | 1 | for status in self.applications: |
|
119 | 1 | if status.application == application.name: |
|
120 | 1 | states = [s for s in status.computers if s.timestamp.active] |
|
121 | 1 | if states: |
|
122 | 1 | states.sort(key=lambda s: s.timestamp, reverse=True) |
|
123 | log.debug( |
||
124 | "%s marked as started on: %s", |
||
125 | 1 | application, |
|
126 | ', '.join(str(s) for s in states), |
||
127 | 1 | ) |
|
128 | 1 | # TODO: consider returning the computer instance? |
|
129 | return states[0].computer |
||
130 | 1 | ||
131 | log.debug("marked as started on: nothing") |
||
132 | return None |
||
133 | 1 | ||
134 | 1 | @log_running |
|
135 | 1 | def is_running(self, application, computer): |
|
136 | 1 | """Determine if an application is logged as running on a computer.""" |
|
137 | 1 | for status in self.applications: |
|
138 | if status.application == application.name: |
||
139 | for state in status.computers: |
||
140 | 1 | if state.computer == computer.name: |
|
141 | return state.timestamp.active |
||
142 | 1 | ||
143 | # Status not found, assume the application is not running |
||
144 | 1 | return False |
|
145 | 1 | ||
146 | def queue(self, application, computer): |
||
147 | 1 | """Record an application as queued for launch on a computer.""" |
|
148 | status = self.find(application) |
||
149 | status.next = computer.name |
||
150 | 1 | ||
151 | 1 | View Code Duplication | @log_starting |
|
|||
152 | 1 | def start(self, application, computer): |
|
153 | 1 | """Record an application as running on a computer.""" |
|
154 | 1 | for status in self.applications: |
|
155 | 1 | if status.application == application.name: |
|
156 | 1 | for state in status.computers: |
|
157 | 1 | if state.computer == computer.name: |
|
158 | self.counter += 1 |
||
159 | 1 | state.timestamp.started = self.counter |
|
160 | return |
||
161 | break |
||
162 | 1 | else: |
|
163 | 1 | status = None |
|
164 | 1 | ||
165 | 1 | # Status not found, add the application/computer as started |
|
166 | 1 | self.counter += 1 |
|
167 | 1 | state = State(computer.name) |
|
168 | 1 | state.timestamp.started = self.counter |
|
169 | if status is None: |
||
170 | 1 | status = Status(application.name) |
|
171 | status.computers.append(state) |
||
172 | 1 | self.applications.append(status) |
|
173 | else: |
||
174 | status.computers.append(state) |
||
175 | 1 | ||
176 | 1 | View Code Duplication | @log_stopping |
177 | 1 | def stop(self, application, computer): |
|
178 | 1 | """Record an application as no longer running on a computer.""" |
|
179 | 1 | for status in self.applications: |
|
180 | 1 | if status.application == application.name: |
|
181 | 1 | for state in status.computers: |
|
182 | 1 | if state.computer == computer.name: |
|
183 | self.counter += 1 |
||
184 | 1 | state.timestamp.stopped = self.counter |
|
185 | return |
||
186 | break |
||
187 | 1 | else: |
|
188 | 1 | status = None |
|
189 | 1 | ||
190 | 1 | # Status not found, add the application/computer as stopped |
|
191 | 1 | self.counter += 1 |
|
192 | 1 | state = State(computer.name) |
|
193 | 1 | state.timestamp.stopped = self.counter |
|
194 | if status is None: |
||
195 | 1 | status = Status(application.name) |
|
196 | status.computers.append(state) |
||
197 | self.applications.append(status) |
||
198 | else: |
||
199 | status.computers.append(state) |
||
200 |