forked from ladybug-tools/honeybee-core
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcheckdup.py
173 lines (161 loc) · 8.03 KB
/
checkdup.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
# coding=utf-8
"""Utilities to check whether there are any duplicate values in a list of ids."""
import collections
def check_duplicate_identifiers(
objects_to_check, raise_exception=True, obj_name='', detailed=False,
code='000000', extension='Core', error_type='Duplicate Object Identifier'):
"""Check whether there are duplicated identifiers across a list of objects.
Args:
objects_to_check: A list of honeybee objects across which duplicate
identifiers will be checked.
raise_exception: Boolean to note whether an exception should be raised if
duplicated identifiers are found. (Default: True).
obj_name: An optional name for the object to be included in the error
message. Fro example, 'Room', 'Face', 'Aperture'.
detailed: Boolean for whether the returned object is a detailed list of
dicts with error info or a string with a message. (Default: False).
code: Text for the error code. (Default: 0000).
extension: Text for the name of the Honeybee extension for which duplicate
identifiers are being evaluated. (Default: Core).
error_type: Text for the type of error. This should be directly linked
to the error code and should simply be a human-readable version of
the error code. (Default: Unknown Error).
Returns:
A message string indicating the duplicated identifiers (if detailed is False)
or a list of dictionaries with information about the duplicated identifiers
(if detailed is True). This string (or list) will be empty if no duplicates
were found.
"""
detailed = False if raise_exception else detailed
obj_id_iter = (obj.identifier for obj in objects_to_check)
dup = [t for t, c in collections.Counter(obj_id_iter).items() if c > 1]
if len(dup) != 0:
if detailed:
# find the object display names
dis_names = []
for obj_id in dup:
dis_name = None
for obj in objects_to_check:
if obj.identifier == obj_id:
dis_name = obj.display_name
dis_names.append(dis_name)
err_list = []
for dup_id, dis_name in zip(dup, dis_names):
msg = 'There is a duplicated {} identifier: {}'.format(obj_name, dup_id)
dup_dict = {
'type': 'ValidationError',
'code': code,
'error_type': error_type,
'extension_type': extension,
'element_type': obj_name,
'element_id': [dup_id],
'message': msg
}
if dis_name is not None:
dup_dict['element_name'] = [dis_name]
err_list.append(dup_dict)
return err_list
msg = 'The following duplicated {} identifiers were found:\n{}'.format(
obj_name, '\n'.join(dup))
if raise_exception:
raise ValueError(msg)
return msg
return [] if detailed else ''
def check_duplicate_identifiers_parent(
objects_to_check, raise_exception=True, obj_name='', detailed=False,
code='000000', extension='Core', error_type='Duplicate Object Identifier'):
"""Check whether there are duplicated identifiers across a list of objects.
The error message will include the identifiers of top-level parents in order
to make it easier to find the duplicated objects in the model.
Args:
objects_to_check: A list of honeybee objects across which duplicate
identifiers will be checked. These objects must have the ability to
have parents for this method to run correctly.
raise_exception: Boolean to note whether an exception should be raised if
duplicated identifiers are found. (Default: True).
obj_name: An optional name for the object to be included in the error
message. For example, 'Room', 'Face', 'Aperture'.
detailed: Boolean for whether the returned object is a detailed list of
dicts with error info or a string with a message. (Default: False).
code: Text for the error code. (Default: 0000).
extension: Text for the name of the Honeybee extension for which duplicate
identifiers are being evaluated. (Default: Core).
error_type: Text for the type of error. This should be directly linked
to the error code and should simply be a human-readable version of
the error code. (Default: Unknown Error).
Returns:
A message string indicating the duplicated identifiers (if detailed is False)
or a list of dictionaries with information about the duplicated identifiers
(if detailed is True). This string (or list) will be empty if no duplicates
were found.
"""
detailed = False if raise_exception else detailed
obj_id_iter = (obj.identifier for obj in objects_to_check)
dup = [t for t, c in collections.Counter(obj_id_iter).items() if c > 1]
if len(dup) != 0:
# find the relevant top-level parents
top_par, dis_names = [], []
for obj_id in dup:
rel_parents, dis_name = [], None
for obj in objects_to_check:
if obj.identifier == obj_id:
dis_name = obj.display_name
if obj.has_parent:
try:
par_obj = obj.top_level_parent
except AttributeError:
par_obj = obj.parent
rel_parents.append(par_obj)
top_par.append(rel_parents)
dis_names.append(dis_name)
# if a detailed dictionary is requested, then create it
if detailed:
err_list = []
for dup_id, dis_name, rel_par in zip(dup, dis_names, top_par):
dup_dict = {
'type': 'ValidationError',
'code': code,
'error_type': error_type,
'extension_type': extension,
'element_type': obj_name,
'element_id': [dup_id]
}
if dis_name is not None:
dup_dict['element_name'] = [dis_name]
msg = 'There is a duplicated {} identifier: {}'.format(obj_name, dup_id)
if len(rel_par) != 0:
dup_dict['top_parents'] = []
msg += '\n Relevant Top-Level Parents:\n'
for par_o in rel_par:
par_dict = {
'parent_type': par_o.__class__.__name__,
'id': par_o.identifier,
'name': par_o.display_name
}
dup_dict['top_parents'].append(par_dict)
msg += ' {} "{}"\n'.format(
par_o.__class__.__name__, par_o.full_id)
dup_dict['message'] = msg
err_list.append(dup_dict)
return err_list
# if just an error message is requested, then build it from the information
msg = 'The following duplicated {} identifiers were found:\n'.format(obj_name)
for obj_id, rel_par in zip(dup, top_par):
obj_msg = obj_id + '\n'
if len(rel_par) != 0:
obj_msg += ' Relevant Top-Level Parents:\n'
for par_o in rel_par:
obj_msg += ' {} "{}"\n'.format(
par_o.__class__.__name__, par_o.full_id)
msg += obj_msg
msg = msg.strip()
if raise_exception:
raise ValueError(msg)
return msg
return [] if detailed else ''
def is_equivalent(object_1, object_2):
"""Check if two objects are equal with an initial check for the same instance.
"""
if object_1 is object_2: # first see if they're the same instance
return True
return object_1 == object_2 # two objects that should have == operators