extract-msg 0.48.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. extract_msg/__init__.py +71 -0
  2. extract_msg/__main__.py +115 -0
  3. extract_msg/_rtf/__init__.py +21 -0
  4. extract_msg/_rtf/create_doc.py +29 -0
  5. extract_msg/_rtf/inject_rtf.py +174 -0
  6. extract_msg/_rtf/token.py +35 -0
  7. extract_msg/_rtf/tokenize_rtf.py +253 -0
  8. extract_msg/attachments/__init__.py +144 -0
  9. extract_msg/attachments/attachment.py +188 -0
  10. extract_msg/attachments/attachment_base.py +730 -0
  11. extract_msg/attachments/broken_att.py +42 -0
  12. extract_msg/attachments/custom_att.py +173 -0
  13. extract_msg/attachments/custom_att_handler/__init__.py +80 -0
  14. extract_msg/attachments/custom_att_handler/custom_handler.py +134 -0
  15. extract_msg/attachments/custom_att_handler/lnk_obj_att.py +109 -0
  16. extract_msg/attachments/custom_att_handler/outlook_image_dib.py +141 -0
  17. extract_msg/attachments/emb_msg_att.py +134 -0
  18. extract_msg/attachments/signed_att.py +234 -0
  19. extract_msg/attachments/unsupported_att.py +40 -0
  20. extract_msg/attachments/web_att.py +74 -0
  21. extract_msg/constants/__init__.py +294 -0
  22. extract_msg/constants/ps.py +46 -0
  23. extract_msg/constants/re.py +42 -0
  24. extract_msg/constants/st.py +110 -0
  25. extract_msg/data/logging-config/logging-nt.json +64 -0
  26. extract_msg/data/logging-config/logging-posix.json +64 -0
  27. extract_msg/encoding/__init__.py +277 -0
  28. extract_msg/encoding/_dt/__init__.py +4 -0
  29. extract_msg/encoding/_dt/_mac_ce.py +8 -0
  30. extract_msg/encoding/_dt/_mac_cyrillic.py +8 -0
  31. extract_msg/encoding/_dt/_mac_greek.py +8 -0
  32. extract_msg/encoding/_dt/_mac_iceland.py +8 -0
  33. extract_msg/encoding/_dt/_mac_turkish.py +8 -0
  34. extract_msg/encoding/_dt/_win874_dec.py +8 -0
  35. extract_msg/encoding/_dt/_win950_dec.py +6 -0
  36. extract_msg/encoding/utils.py +329 -0
  37. extract_msg/enums.py +2067 -0
  38. extract_msg/exceptions.py +170 -0
  39. extract_msg/msg_classes/__init__.py +49 -0
  40. extract_msg/msg_classes/appointment.py +195 -0
  41. extract_msg/msg_classes/calendar.py +92 -0
  42. extract_msg/msg_classes/calendar_base.py +496 -0
  43. extract_msg/msg_classes/contact.py +1335 -0
  44. extract_msg/msg_classes/journal.py +131 -0
  45. extract_msg/msg_classes/meeting_cancellation.py +108 -0
  46. extract_msg/msg_classes/meeting_exception.py +55 -0
  47. extract_msg/msg_classes/meeting_forward.py +115 -0
  48. extract_msg/msg_classes/meeting_related.py +65 -0
  49. extract_msg/msg_classes/meeting_request.py +189 -0
  50. extract_msg/msg_classes/meeting_response.py +74 -0
  51. extract_msg/msg_classes/message.py +12 -0
  52. extract_msg/msg_classes/message_base.py +1382 -0
  53. extract_msg/msg_classes/message_signed.py +17 -0
  54. extract_msg/msg_classes/message_signed_base.py +168 -0
  55. extract_msg/msg_classes/msg.py +1172 -0
  56. extract_msg/msg_classes/post.py +53 -0
  57. extract_msg/msg_classes/sticky_note.py +67 -0
  58. extract_msg/msg_classes/task.py +363 -0
  59. extract_msg/msg_classes/task_request.py +86 -0
  60. extract_msg/null_date.py +34 -0
  61. extract_msg/ole_writer.py +1086 -0
  62. extract_msg/open_msg.py +204 -0
  63. extract_msg/properties/__init__.py +23 -0
  64. extract_msg/properties/named.py +481 -0
  65. extract_msg/properties/prop.py +506 -0
  66. extract_msg/properties/properties_store.py +476 -0
  67. extract_msg/py.typed +0 -0
  68. extract_msg/recipient.py +362 -0
  69. extract_msg/structures/__init__.py +34 -0
  70. extract_msg/structures/_helpers.py +345 -0
  71. extract_msg/structures/business_card.py +422 -0
  72. extract_msg/structures/cfoas.py +108 -0
  73. extract_msg/structures/contact_link_entry.py +51 -0
  74. extract_msg/structures/dev_mode_a.py +621 -0
  75. extract_msg/structures/dv_target_device.py +168 -0
  76. extract_msg/structures/entry_id.py +697 -0
  77. extract_msg/structures/misc_id.py +222 -0
  78. extract_msg/structures/mon_stream.py +55 -0
  79. extract_msg/structures/odt.py +74 -0
  80. extract_msg/structures/ole_pres.py +302 -0
  81. extract_msg/structures/ole_stream_struct.py +117 -0
  82. extract_msg/structures/recurrence_pattern.py +199 -0
  83. extract_msg/structures/report_tag.py +127 -0
  84. extract_msg/structures/system_time.py +158 -0
  85. extract_msg/structures/time_zone_definition.py +111 -0
  86. extract_msg/structures/time_zone_struct.py +102 -0
  87. extract_msg/structures/toc_entry.py +137 -0
  88. extract_msg/structures/tz_rule.py +148 -0
  89. extract_msg/utils.py +1270 -0
  90. extract_msg-0.48.2.dist-info/LICENSE.txt +674 -0
  91. extract_msg-0.48.2.dist-info/METADATA +321 -0
  92. extract_msg-0.48.2.dist-info/RECORD +95 -0
  93. extract_msg-0.48.2.dist-info/WHEEL +5 -0
  94. extract_msg-0.48.2.dist-info/entry_points.txt +2 -0
  95. extract_msg-0.48.2.dist-info/top_level.txt +1 -0
@@ -0,0 +1,481 @@
1
+ from __future__ import annotations
2
+
3
+
4
+ __all__ = [
5
+ 'Named',
6
+ 'NamedProperties',
7
+ 'NamedPropertyBase',
8
+ 'NumericalNamedProperty',
9
+ 'StringNamedProperty',
10
+ ]
11
+
12
+
13
+ import abc
14
+ import copy
15
+ import logging
16
+ import pprint
17
+ import weakref
18
+
19
+ from typing import (
20
+ Any, Dict, Iterable, Iterator, List, Optional, Tuple, TYPE_CHECKING,
21
+ TypeVar, Union
22
+ )
23
+
24
+ from .. import constants
25
+ from ..enums import ErrorBehavior, NamedPropertyType
26
+ from ..exceptions import InvalidPropertyIdError, StandardViolationError
27
+ from ..utils import bytesToGuid, divide, msgPathToString, verifyPropertyId
28
+ from compressed_rtf.crc32 import crc32
29
+
30
+
31
+ # Allow for nice type checking.
32
+ if TYPE_CHECKING:
33
+ from ..msg_classes.msg import MSGFile
34
+ from ..attachments.attachment_base import AttachmentBase
35
+
36
+ logger = logging.getLogger(__name__)
37
+ logger.addHandler(logging.NullHandler())
38
+
39
+ _T = TypeVar('_T')
40
+
41
+
42
+ class Named:
43
+ """
44
+ Class for handling access to the named properties themselves.
45
+ """
46
+
47
+ __dir = '__nameid_version1.0'
48
+
49
+ def __init__(self, msg: MSGFile):
50
+ self.__msg = weakref.ref(msg)
51
+ # Get the basic streams. If all are emtpy, then nothing to do.
52
+ guidStream = self.getStream('__substg1.0_00020102')
53
+ entryStream = self.getStream('__substg1.0_00030102')
54
+ self.guidStream = guidStream
55
+ self.entryStream = entryStream
56
+ self.namesStream = self.getStream('__substg1.0_00040102')
57
+
58
+ self.__propertiesDict: Dict[Tuple[str, str], NamedPropertyBase] = {}
59
+
60
+ self.__streamIDDict: Dict[str, Tuple[str, str]] = {}
61
+
62
+ if guidStream is None:
63
+ if ErrorBehavior.STANDARDS_VIOLATION in msg.errorBehavior:
64
+ logger.warning('Standards Violation: Guid stream missing from named properties.')
65
+ guidStream = b''
66
+ else:
67
+ raise StandardViolationError('Guid stream missing from named properties.')
68
+
69
+ if entryStream is None:
70
+ if ErrorBehavior.STANDARDS_VIOLATION in msg.errorBehavior:
71
+ logger.warning('Standards Violation: Entry stream missing from named properties.')
72
+ entryStream = b''
73
+ else:
74
+ raise StandardViolationError('Entry stream missing from named properties.')
75
+
76
+ if self.namesStream is None:
77
+ if ErrorBehavior.STANDARDS_VIOLATION in msg.errorBehavior:
78
+ logger.error('Standards Violation: Guid stream missing from named properties. Will not parse named properties.')
79
+ # Return immediately since the entry stream will likely fail.
80
+ return
81
+ else:
82
+ raise StandardViolationError('Guid stream missing from named properties.')
83
+
84
+ # Check that we even have any entries. If there are none, nothing to do.
85
+ if entryStream:
86
+ guids = tuple([None, constants.ps.PS_MAPI, constants.ps.PS_PUBLIC_STRINGS] + [bytesToGuid(x) for x in divide(guidStream, 16)])
87
+ entries: List[Dict[str, Any]]= []
88
+ for rawStream in divide(entryStream, 8):
89
+ tmp = constants.st.ST_NP_ENT.unpack(rawStream)
90
+ entry = {
91
+ 'id': tmp[0],
92
+ 'pid': tmp[2],
93
+ 'guid_index': tmp[1] >> 1,
94
+ 'pkind': NamedPropertyType(tmp[1] & 1), # 0 if numerical, 1 if string.
95
+ 'rawStream': rawStream,
96
+ }
97
+ entry['guid'] = guids[entry['guid_index']]
98
+ entries.append(entry)
99
+
100
+ properties: List[NamedPropertyBase] = []
101
+
102
+ for entry in entries:
103
+ if entry['pkind'] == NamedPropertyType.STRING_NAMED:
104
+ name = None
105
+ try:
106
+ name = self.__getName(entry['id'])
107
+ except ValueError as e:
108
+ if ErrorBehavior.NAMED_NAME_STREAM in msg.errorBehavior:
109
+ logger.error(f'Dropping named property because it failed to acquire name from name stream: {e}')
110
+ else:
111
+ raise
112
+
113
+ if name:
114
+ properties.append(StringNamedProperty(entry, name))
115
+ else:
116
+ properties.append(NumericalNamedProperty(entry))
117
+
118
+ for property in properties:
119
+ id_ = property.identifier
120
+ self.__propertiesDict[id_] = property
121
+ self.__streamIDDict[property.propertyStreamID] = id_
122
+
123
+ def __contains__(self, key) -> bool:
124
+ return key in self.__propertiesDict
125
+
126
+ def __getitem__(self, propertyName: Tuple[str, str]) -> NamedPropertyBase:
127
+ # Validate the key.
128
+ if not hasattr(propertyName, '__len__') or len(propertyName) != 2:
129
+ raise TypeError('Named property key must be a tuple of two strings.')
130
+
131
+ # Case insensitive search of the dictionary.
132
+ propertyName = (propertyName[0].upper(), propertyName[1].upper())
133
+ for key in self.__propertiesDict.keys():
134
+ if propertyName == (key[0].upper(), key[1].upper()):
135
+ return self.__propertiesDict[key]
136
+
137
+ raise KeyError(propertyName)
138
+
139
+ def __iter__(self) -> Iterator[Tuple[str, str]]:
140
+ return self.__propertiesDict.__iter__()
141
+
142
+ def __len__(self) -> int:
143
+ return self.__propertiesDict.__len__()
144
+
145
+ def __getName(self, offset: int) -> str:
146
+ """
147
+ Parses the offset into the named stream and returns the name found.
148
+ """
149
+ # We used to parse names by handing it as an array, as specified by the
150
+ # documentation, but this new method allows for a little bit more wiggle
151
+ # room in terms of what is accepted by the module.
152
+ if offset & 3 != 0:
153
+ # If the offset is not a multiple of 4, that is an error, but we are
154
+ # reducing it to a warning.
155
+ logger.warning(f'Malformed named properties detected due to bad offset ({offset}). Ignoring.')
156
+ # Check that offset is in string stream.
157
+ if offset > len(self.namesStream):
158
+ raise ValueError('Failed to parse named property: offset was not in string stream.')
159
+
160
+ # Get the length, in bytes, of the string.
161
+ length = constants.st.ST_LE_I32.unpack(self.namesStream[offset:offset + 4])[0]
162
+ offset += 4
163
+
164
+ # Make sure the string can be read entirely. If it can't, something was
165
+ # corrupt.
166
+ if offset + length > len(self.namesStream):
167
+ raise ValueError(f'Failed to parse named property: length ({length}) of string overflows the string stream. This is probably due to a bad offset.')
168
+
169
+ return self.namesStream[offset:offset + length].decode('utf-16-le')
170
+
171
+ def exists(self, filename: constants.MSG_PATH) -> bool:
172
+ """
173
+ Checks if stream exists inside the named properties folder.
174
+
175
+ :raises ReferenceError: The associated ``MSGFile`` instance has been
176
+ garbage collected.
177
+ """
178
+ if (msg := self.__msg()) is None:
179
+ raise ReferenceError('The MSGFile for this Named instance has been garbage collected.')
180
+ return msg.exists([self.__dir, msgPathToString(filename)], False)
181
+
182
+ def get(self, propertyName: Tuple[str, str], default: _T = None) -> Union[NamedPropertyBase, _T]:
183
+ """
184
+ Tries to get a named property based on its key.
185
+
186
+ Returns :param default: if not found. Key is a tuple of the name and
187
+ the property set GUID.
188
+ """
189
+ try:
190
+ return self[propertyName]
191
+ except KeyError:
192
+ return default
193
+
194
+ def getPropNameByStreamID(self, streamID: Union[int, str]) -> Optional[Tuple[str, str]]:
195
+ """
196
+ Gets the name of a property (as a key for the internal dict) that is stored in the specified stream.
197
+
198
+ Useful for determining if a stream/property stream entry is a named
199
+ property.
200
+
201
+ :param streamID: A 4 hex character identifier that will be checked. May
202
+ also be an integer that can convert to 4 hex characters.
203
+
204
+ :returns: The name, if the stream is a named property, otherwise
205
+ ``None``.
206
+
207
+ :raises InvalidPropertyIdError: The Stream ID is invalid.
208
+ :raises TypeError: The Stream ID is not a valid type.
209
+ """
210
+ if isinstance(streamID, int):
211
+ if streamID < 0x8000:
212
+ # Definitely doesn't refer to a name property, so just return
213
+ # None.
214
+ return None
215
+ if streamID > 0xFFFF:
216
+ raise InvalidPropertyIdError('Stream ID is out of range.')
217
+ streamID = f'{streamID:04X}'
218
+ elif isinstance(streamID, str):
219
+ verifyPropertyId(streamID)
220
+ else:
221
+ raise TypeError(':param streamID: MUST be an int or str.')
222
+
223
+ return self.__streamIDDict.get(streamID)
224
+
225
+ def getStream(self, filename: constants.MSG_PATH) -> Optional[bytes]:
226
+ """
227
+ Gets a binary representation of the requested stream.
228
+
229
+ This should ALWAYS return a ``bytes`` object if it was found, otherwise
230
+ returns ``None``.
231
+
232
+ :raises ReferenceError: The associated ``MSGFile`` instance has been
233
+ garbage collected.
234
+ """
235
+ if (msg := self.__msg()) is None:
236
+ raise ReferenceError('The MSGFile for this Named instance has been garbage collected.')
237
+ return msg.getStream([self.__dir, msgPathToString(filename)], False)
238
+
239
+ def items(self) -> Iterable[Tuple[Tuple[str, str], NamedPropertyBase]]:
240
+ return self.__propertiesDict.items()
241
+
242
+ def keys(self) -> Iterable[Tuple[str, str]]:
243
+ return self.__propertiesDict.keys()
244
+
245
+ def pprintKeys(self) -> None:
246
+ """
247
+ Uses the pprint function on a sorted list of keys.
248
+ """
249
+ pprint.pprint(sorted(self.__propertiesDict.keys()))
250
+
251
+ def values(self) -> Iterable[NamedPropertyBase]:
252
+ return self.__propertiesDict.values()
253
+
254
+ @property
255
+ def dir(self):
256
+ """
257
+ Returns the directory inside the MSG file where the named properties are located.
258
+ """
259
+ return self.__dir
260
+
261
+ @property
262
+ def msg(self) -> MSGFile:
263
+ """
264
+ Returns the Message instance the attachment belongs to.
265
+
266
+ :raises ReferenceError: The associated ``MSGFile`` instance has been
267
+ garbage collected.
268
+ """
269
+ if (msg := self.__msg()) is None:
270
+ raise ReferenceError('The MSGFile for this Named instance has been garbage collected.')
271
+ return msg
272
+
273
+ @property
274
+ def namedProperties(self) -> Dict[Tuple[str, str], NamedPropertyBase]:
275
+ """
276
+ Returns a copy of the dictionary containing all the named properties.
277
+ """
278
+ return copy.deepcopy(self.__propertiesDict)
279
+
280
+
281
+
282
+ class NamedProperties:
283
+ """
284
+ An instance that uses a Named instance and an extract-msg class to read the
285
+ data of named properties.
286
+ """
287
+ def __init__(self, named: Named, streamSource: Union[MSGFile, AttachmentBase]):
288
+ """
289
+ :param named: The Named instance to refer to for named properties
290
+ entries.
291
+ :param streamSource: The source to use for acquiring the data of a named
292
+ property.
293
+ """
294
+ self.__named = named
295
+ self.__streamSource = weakref.ref(streamSource)
296
+
297
+ def __getitem__(self, item: Union[Tuple[str, str], NamedPropertyBase]):
298
+ """
299
+ Get a named property using the [] operator. Item must be a named
300
+ property instance or a tuple with 2 items: the name and the GUID string.
301
+
302
+ :raises ReferenceError: The associated instance for getting actual
303
+ property data has been garbage collected.
304
+ """
305
+ if (source := self.__streamSource()) is None:
306
+ raise ReferenceError('The stream source for the NamedProperties instance has been garbage collected.')
307
+ if isinstance(item, NamedPropertyBase):
308
+ return source._getTypedData(item.propertyStreamID)
309
+ else:
310
+ return source._getTypedData(self.__named[item].propertyStreamID)
311
+
312
+ def get(self, item: Union[Tuple[str, str], NamedPropertyBase], default: _T = None) -> Union[Any, _T]:
313
+ """
314
+ Get a named property, returning the value of :param default: if not
315
+ found. Item must be a tuple with 2 items: the name and the GUID string.
316
+
317
+ :raises ReferenceError: The associated instance for getting actual
318
+ property data has been garbage collected.
319
+ """
320
+ try:
321
+ return self[item]
322
+ except KeyError:
323
+ return default
324
+
325
+
326
+
327
+ class NamedPropertyBase(abc.ABC):
328
+ def __init__(self, entry: Dict[str, Any]):
329
+ self.__entry = entry
330
+ self.__guidIndex = entry['guid_index']
331
+ self.__namedPropertyID = entry['pid']
332
+ self.__guid = entry['guid']
333
+ self.__propertyStreamID = f'{0x8000 + self.__namedPropertyID:04X}'
334
+
335
+ @property
336
+ def guid(self) -> str:
337
+ """
338
+ The guid of the property's property set.
339
+ """
340
+ return self.__guid
341
+
342
+ @property
343
+ def guidIndex(self) -> int:
344
+ """
345
+ The guid index of the property's property set.
346
+ """
347
+ return self.__guidIndex
348
+
349
+ @property
350
+ @abc.abstractmethod
351
+ def identifier(self) -> Tuple[str, str]:
352
+ """
353
+ An identifier that can be used to full identify the property.
354
+ """
355
+
356
+ @property
357
+ def namedPropertyID(self) -> int:
358
+ """
359
+ The named property id.
360
+ """
361
+ return self.__namedPropertyID
362
+
363
+ @property
364
+ def propertyStreamID(self) -> str:
365
+ """
366
+ An ID usable for grabbing the value stream.
367
+ """
368
+ return self.__propertyStreamID
369
+
370
+ @property
371
+ def rawEntry(self) -> Dict[str, Any]:
372
+ return copy.deepcopy(self.__entry)
373
+
374
+ @property
375
+ def rawEntryStream(self) -> bytes:
376
+ """
377
+ The raw data used for the entry.
378
+ """
379
+ return self.__entry['rawStream']
380
+
381
+ @property
382
+ @abc.abstractmethod
383
+ def type(self) -> NamedPropertyType:
384
+ """
385
+ Returns the type of the named property. This will be a member of the
386
+ NamedPropertyType enum.
387
+ """
388
+
389
+
390
+
391
+ class StringNamedProperty(NamedPropertyBase):
392
+ def __init__(self, entry: Dict, name: str):
393
+ super().__init__(entry)
394
+ self.__name = name
395
+
396
+ # Finally got this to be correct after asking about it on a Microsoft
397
+ # forum. Apparently it uses the same CRC-32 as the Compressed RTF
398
+ # standard does, so we can just use the function defined in the
399
+ # compressed-rtf Python module.
400
+ #
401
+ # First thing to note is that the name should only ever be lowered if it
402
+ # is part of the PS_INTERNET_HEADERS property set **AND** it is
403
+ # generated by certain versions of Outlook. As such, a little bit of
404
+ # additional code will need to run to determine exactly what the stream
405
+ # ID should be if it is in that property set.
406
+ if self.guid == constants.ps.PS_INTERNET_HEADERS:
407
+ # To be sure if it needs to be lower the most effective method would
408
+ # be to just get the Stream ID and then check if the entry is in
409
+ # there. If it isn't, then check the regular case and see. If it is
410
+ # not in either... well, we don't use it for anything so it will
411
+ # just be a warning, and the Stream ID will be set to 0.
412
+ #
413
+ # TODO: Unfortunately, doing this will need to be put off until a
414
+ # different version, preferably after Python 2 support is removed,
415
+ # as this will require restructuring a lot of internal code. For now
416
+ # we just assume that it is lowercase.
417
+ self.__streamID = 0x1000 + (crc32(name.lower().encode('utf-16-le')) ^ (self.guidIndex << 1 | 1)) % 0x1F
418
+
419
+ else:
420
+ # No special logic here to determine what to do.
421
+ self.__streamID = 0x1000 + (crc32(name.encode('utf-16-le')) ^ (self.guidIndex << 1 | 1)) % 0x1F
422
+
423
+ @property
424
+ def identifier(self) -> Tuple[str, str]:
425
+ return (self.name, self.guid)
426
+
427
+ @property
428
+ def name(self) -> str:
429
+ """
430
+ The name of the property.
431
+ """
432
+ return self.__name
433
+
434
+ @property
435
+ def streamID(self) -> int:
436
+ """
437
+ Returns the streamID of the named property. This may not be accurate.
438
+ """
439
+ return self.__streamID
440
+
441
+ @property
442
+ def type(self) -> NamedPropertyType:
443
+ """
444
+ Returns the type of the named property. This will be a member of the
445
+ NamedPropertyType enum.
446
+ """
447
+ return NamedPropertyType.STRING_NAMED
448
+
449
+
450
+
451
+ class NumericalNamedProperty(NamedPropertyBase):
452
+ def __init__(self, entry: Dict):
453
+ super().__init__(entry)
454
+ self.__propertyID = f'{entry["id"]:04X}'
455
+ self.__streamID = 0x1000 + (entry['id'] ^ (self.guidIndex << 1)) % 0x1F
456
+
457
+ @property
458
+ def identifier(self) -> Tuple[str, str]:
459
+ return (self.propertyID, self.guid)
460
+
461
+ @property
462
+ def propertyID(self) -> str:
463
+ """
464
+ The actualy property id of the named property.
465
+ """
466
+ return self.__propertyID
467
+
468
+ @property
469
+ def streamID(self) -> int:
470
+ """
471
+ Returns the streamID of the named property. This may not be accurate.
472
+ """
473
+ return self.__streamID
474
+
475
+ @property
476
+ def type(self) -> NamedPropertyType:
477
+ """
478
+ Returns the type of the named property. This will be a member of the
479
+ NamedPropertyType enum.
480
+ """
481
+ return NamedPropertyType.NUMERICAL_NAMED