@sw-tsdk/plugin-connector 3.13.1 → 3.13.2-next.3dfd44a
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.
- package/README.md +18 -18
- package/lib/commands/connector/build.js +168 -44
- package/lib/commands/connector/build.js.map +1 -1
- package/lib/commands/connector/sign.js +108 -12
- package/lib/commands/connector/sign.js.map +1 -1
- package/lib/commands/connector/validate.js +110 -10
- package/lib/commands/connector/validate.js.map +1 -1
- package/lib/commands/migrator/convert.d.ts +3 -0
- package/lib/commands/migrator/convert.js +201 -20
- package/lib/commands/migrator/convert.js.map +1 -1
- package/lib/templates/migrator-runners/plugin_override.txt +76 -4
- package/lib/templates/migrator-runners/runner_override.txt +30 -0
- package/lib/templates/migrator-runners/script_override.txt +77 -5
- package/lib/templates/swimlane/__init__.py +18 -0
- package/lib/templates/swimlane/core/__init__.py +0 -0
- package/lib/templates/swimlane/core/adapters/__init__.py +10 -0
- package/lib/templates/swimlane/core/adapters/app.py +59 -0
- package/lib/templates/swimlane/core/adapters/app_revision.py +49 -0
- package/lib/templates/swimlane/core/adapters/helper.py +84 -0
- package/lib/templates/swimlane/core/adapters/record.py +468 -0
- package/lib/templates/swimlane/core/adapters/record_revision.py +43 -0
- package/lib/templates/swimlane/core/adapters/report.py +65 -0
- package/lib/templates/swimlane/core/adapters/task.py +58 -0
- package/lib/templates/swimlane/core/adapters/usergroup.py +183 -0
- package/lib/templates/swimlane/core/bulk.py +48 -0
- package/lib/templates/swimlane/core/cache.py +165 -0
- package/lib/templates/swimlane/core/client.py +466 -0
- package/lib/templates/swimlane/core/cursor.py +100 -0
- package/lib/templates/swimlane/core/fields/__init__.py +46 -0
- package/lib/templates/swimlane/core/fields/attachment.py +82 -0
- package/lib/templates/swimlane/core/fields/base/__init__.py +15 -0
- package/lib/templates/swimlane/core/fields/base/cursor.py +90 -0
- package/lib/templates/swimlane/core/fields/base/field.py +149 -0
- package/lib/templates/swimlane/core/fields/base/multiselect.py +116 -0
- package/lib/templates/swimlane/core/fields/comment.py +48 -0
- package/lib/templates/swimlane/core/fields/datetime.py +112 -0
- package/lib/templates/swimlane/core/fields/history.py +28 -0
- package/lib/templates/swimlane/core/fields/list.py +266 -0
- package/lib/templates/swimlane/core/fields/number.py +38 -0
- package/lib/templates/swimlane/core/fields/reference.py +169 -0
- package/lib/templates/swimlane/core/fields/text.py +30 -0
- package/lib/templates/swimlane/core/fields/tracking.py +10 -0
- package/lib/templates/swimlane/core/fields/usergroup.py +137 -0
- package/lib/templates/swimlane/core/fields/valueslist.py +70 -0
- package/lib/templates/swimlane/core/resolver.py +46 -0
- package/lib/templates/swimlane/core/resources/__init__.py +0 -0
- package/lib/templates/swimlane/core/resources/app.py +136 -0
- package/lib/templates/swimlane/core/resources/app_revision.py +43 -0
- package/lib/templates/swimlane/core/resources/attachment.py +64 -0
- package/lib/templates/swimlane/core/resources/base.py +55 -0
- package/lib/templates/swimlane/core/resources/comment.py +33 -0
- package/lib/templates/swimlane/core/resources/record.py +499 -0
- package/lib/templates/swimlane/core/resources/record_revision.py +44 -0
- package/lib/templates/swimlane/core/resources/report.py +259 -0
- package/lib/templates/swimlane/core/resources/revision_base.py +69 -0
- package/lib/templates/swimlane/core/resources/task.py +16 -0
- package/lib/templates/swimlane/core/resources/usergroup.py +166 -0
- package/lib/templates/swimlane/core/search.py +31 -0
- package/lib/templates/swimlane/core/wrappedsession.py +12 -0
- package/lib/templates/swimlane/exceptions.py +191 -0
- package/lib/templates/swimlane/utils/__init__.py +132 -0
- package/lib/templates/swimlane/utils/date_validator.py +4 -0
- package/lib/templates/swimlane/utils/list_validator.py +7 -0
- package/lib/templates/swimlane/utils/str_validator.py +10 -0
- package/lib/templates/swimlane/utils/version.py +101 -0
- package/lib/transformers/base-transformer.js +61 -14
- package/lib/transformers/base-transformer.js.map +1 -1
- package/lib/transformers/connector-generator.d.ts +104 -2
- package/lib/transformers/connector-generator.js +1234 -51
- package/lib/transformers/connector-generator.js.map +1 -1
- package/lib/types/migrator-types.d.ts +22 -0
- package/lib/types/migrator-types.js.map +1 -1
- package/oclif.manifest.json +1 -1
- package/package.json +6 -6
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import six
|
|
2
|
+
|
|
3
|
+
from swimlane.exceptions import ValidationError
|
|
4
|
+
from .base import MultiSelectField
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class ValuesListField(MultiSelectField):
|
|
8
|
+
|
|
9
|
+
field_type = (
|
|
10
|
+
'Core.Models.Fields.ValuesListField, Core',
|
|
11
|
+
'Core.Models.Fields.ValuesList.ValuesListField, Core'
|
|
12
|
+
)
|
|
13
|
+
supported_types = six.string_types
|
|
14
|
+
|
|
15
|
+
def __init__(self, *args, **kwargs):
|
|
16
|
+
"""Map names to IDs for use in field rehydration"""
|
|
17
|
+
super(ValuesListField, self).__init__(*args, **kwargs)
|
|
18
|
+
self.selection_to_id_map = {f['name']: f['id'] for f in self.field_definition['values']}
|
|
19
|
+
|
|
20
|
+
def validate_value(self, value):
|
|
21
|
+
"""Validate provided value is one of the valid options"""
|
|
22
|
+
super(ValuesListField, self).validate_value(value)
|
|
23
|
+
|
|
24
|
+
if value is not None:
|
|
25
|
+
if value not in self.selection_to_id_map:
|
|
26
|
+
raise ValidationError(
|
|
27
|
+
self.record,
|
|
28
|
+
'Field "{}" invalid value "{}". Valid options: {}'.format(
|
|
29
|
+
self.name,
|
|
30
|
+
value,
|
|
31
|
+
', '.join(self.selection_to_id_map.keys())
|
|
32
|
+
)
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
def get_batch_representation(self):
|
|
36
|
+
"""Return best batch process representation of field value"""
|
|
37
|
+
return self.get_swimlane()
|
|
38
|
+
|
|
39
|
+
def cast_to_python(self, value):
|
|
40
|
+
"""Store actual value as internal representation"""
|
|
41
|
+
if value is not None:
|
|
42
|
+
if value['value'] in self.selection_to_id_map:
|
|
43
|
+
return value['value']
|
|
44
|
+
|
|
45
|
+
return None
|
|
46
|
+
|
|
47
|
+
def cast_to_swimlane(self, value):
|
|
48
|
+
"""Rehydrate value back as full JSON representation"""
|
|
49
|
+
if value is None:
|
|
50
|
+
return value
|
|
51
|
+
if value not in self.selection_to_id_map:
|
|
52
|
+
return None
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
'$type': 'Core.Models.Record.ValueSelection, Core',
|
|
56
|
+
'id': self.selection_to_id_map[value],
|
|
57
|
+
'value': value
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
def cast_to_report(self, value):
|
|
61
|
+
"""Report format uses only the value's id"""
|
|
62
|
+
value = super(ValuesListField, self).cast_to_report(value)
|
|
63
|
+
|
|
64
|
+
if value:
|
|
65
|
+
return value['id']
|
|
66
|
+
|
|
67
|
+
def cast_to_bulk_modify(self, value):
|
|
68
|
+
"""Bulk modify uses the normal Swimlane representation"""
|
|
69
|
+
self.validate_value(value)
|
|
70
|
+
return self.cast_to_swimlane(value)
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import weakref
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class SwimlaneResolver(object):
|
|
5
|
+
"""Provides automatic weakref resolution for Swimlane client to avoid circular references and
|
|
6
|
+
memory leaks """
|
|
7
|
+
|
|
8
|
+
def __init__(self, swimlane):
|
|
9
|
+
self.__ref_swimlane = weakref.ref(swimlane) if swimlane else swimlane
|
|
10
|
+
|
|
11
|
+
@property
|
|
12
|
+
def _swimlane(self):
|
|
13
|
+
"""Transparently resolve the swimlane weakref"""
|
|
14
|
+
if not self.__ref_swimlane:
|
|
15
|
+
referent = self.__ref_swimlane
|
|
16
|
+
else:
|
|
17
|
+
referent = self.__ref_swimlane()
|
|
18
|
+
if referent is None:
|
|
19
|
+
raise ReferenceError("The swimlane object has been garbage collected. The Swimlane "
|
|
20
|
+
"object uses weak references to avoid memory leaks. The object "
|
|
21
|
+
"gets garbage collected when there are no more references to it. "
|
|
22
|
+
"See the weakref documentation for more information.")
|
|
23
|
+
return referent
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class AppResolver(SwimlaneResolver):
|
|
27
|
+
"""Provides automatic weakref resolution for Swimlane client and App instance"""
|
|
28
|
+
|
|
29
|
+
def __init__(self, app):
|
|
30
|
+
super(AppResolver, self).__init__(app._swimlane)
|
|
31
|
+
|
|
32
|
+
self.__ref_app = weakref.ref(app) if app else app
|
|
33
|
+
|
|
34
|
+
@property
|
|
35
|
+
def _app(self):
|
|
36
|
+
if not self.__ref_app:
|
|
37
|
+
referent = self.__ref_app
|
|
38
|
+
else:
|
|
39
|
+
referent = self.__ref_app()
|
|
40
|
+
if referent is None:
|
|
41
|
+
raise ReferenceError(
|
|
42
|
+
"The App object of the Swimlane Object has been garbage collected. Both the App "
|
|
43
|
+
"object and the Swimlane object use weak references to avoid memory leaks. The "
|
|
44
|
+
"instance gets garbage collected when there are no more strong references to it. "
|
|
45
|
+
"See the weakref documentation for more information.")
|
|
46
|
+
return referent
|
|
File without changes
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
from functools import total_ordering
|
|
2
|
+
|
|
3
|
+
import six
|
|
4
|
+
import pendulum
|
|
5
|
+
|
|
6
|
+
from swimlane.exceptions import UnknownField
|
|
7
|
+
from .base import APIResource
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@total_ordering
|
|
11
|
+
class App(APIResource):
|
|
12
|
+
"""A single App record instance
|
|
13
|
+
|
|
14
|
+
Used lookup field definitions and retrieve/create child Record instances
|
|
15
|
+
|
|
16
|
+
Attributes:
|
|
17
|
+
name (str): App name
|
|
18
|
+
acronym (str): App acronym
|
|
19
|
+
description (str): App description
|
|
20
|
+
id (str): Full App ID
|
|
21
|
+
tracking_id (str): App tracking ID
|
|
22
|
+
records (RecordAdapter): :class:`~swimlane.core.adapters.record.RecordAdapter` configured for current App
|
|
23
|
+
reports (ReportAdapter): :class:`~swimlane.core.adapters.report.ReportAdapter` configured for current App
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
_type = 'Core.Models.Application.Application, Core'
|
|
27
|
+
|
|
28
|
+
def __init__(self, swimlane, raw):
|
|
29
|
+
super(App, self).__init__(swimlane, raw)
|
|
30
|
+
|
|
31
|
+
self.acronym = self._raw['acronym']
|
|
32
|
+
self.name = self._raw['name']
|
|
33
|
+
self.description = self._raw.get('description', '')
|
|
34
|
+
self.id = self._raw['id']
|
|
35
|
+
self.tracking_id = self._raw.get('trackingFieldId')
|
|
36
|
+
|
|
37
|
+
self._fields_by_id = dict()
|
|
38
|
+
self._fields_by_name = dict()
|
|
39
|
+
self._defaults = dict()
|
|
40
|
+
|
|
41
|
+
for field in self._raw['fields']:
|
|
42
|
+
self._fields_by_id[field['id']] = field
|
|
43
|
+
self._fields_by_name[field['name']] = field
|
|
44
|
+
if 'fieldType' in field and field['fieldType'] == "valuesList":
|
|
45
|
+
selection_type = field['selectionType']
|
|
46
|
+
for value in field['values']:
|
|
47
|
+
if 'selected' in value and value['selected']:
|
|
48
|
+
if selection_type == 'single':
|
|
49
|
+
self._defaults[field['name']] = value['name']
|
|
50
|
+
break
|
|
51
|
+
else:
|
|
52
|
+
default = self._defaults.get(field['name'], list())
|
|
53
|
+
default.extend([value['name']])
|
|
54
|
+
self._defaults[field['name']] = default
|
|
55
|
+
if 'fieldType' in field and field['fieldType'] == "date":
|
|
56
|
+
default_value_type = field['defaultValueType']
|
|
57
|
+
if default_value_type == 'specific':
|
|
58
|
+
self._defaults[field['name']] = pendulum.parse(field['defaultValue'])
|
|
59
|
+
|
|
60
|
+
self._keys_to_field_names = {}
|
|
61
|
+
for name, field_def in six.iteritems(self._fields_by_name):
|
|
62
|
+
# Include original name to simplify name resolution
|
|
63
|
+
self._keys_to_field_names[name] = name
|
|
64
|
+
key = field_def.get('key')
|
|
65
|
+
if key:
|
|
66
|
+
self._keys_to_field_names[key] = name
|
|
67
|
+
|
|
68
|
+
# Avoid circular import
|
|
69
|
+
from swimlane.core.adapters import RecordAdapter, ReportAdapter, AppRevisionAdapter
|
|
70
|
+
self.records = RecordAdapter(self)
|
|
71
|
+
self.reports = ReportAdapter(self)
|
|
72
|
+
self.revisions = AppRevisionAdapter(self)
|
|
73
|
+
|
|
74
|
+
def __str__(self):
|
|
75
|
+
return '{self.name} ({self.acronym})'.format(self=self)
|
|
76
|
+
|
|
77
|
+
def __hash__(self):
|
|
78
|
+
return hash((self.id, self.name))
|
|
79
|
+
|
|
80
|
+
def __lt__(self, other):
|
|
81
|
+
if not isinstance(other, self.__class__):
|
|
82
|
+
raise TypeError('Comparisons not supported between instances of "{}" and "{}"'.format(
|
|
83
|
+
other.__class__.__name__,
|
|
84
|
+
self.__class__.__name__
|
|
85
|
+
))
|
|
86
|
+
|
|
87
|
+
return self.name < other.name
|
|
88
|
+
|
|
89
|
+
def get_cache_index_keys(self):
|
|
90
|
+
"""Return all fields available when retrieving apps"""
|
|
91
|
+
return {
|
|
92
|
+
'id': self.id,
|
|
93
|
+
'name': self.name,
|
|
94
|
+
'acroynm': self.acronym
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
def resolve_field_name(self, field_key):
|
|
98
|
+
"""Return the field name matching the given key or None. Searches field keys first, falls back to field names"""
|
|
99
|
+
return self._keys_to_field_names.get(field_key)
|
|
100
|
+
|
|
101
|
+
def get_field_definition_by_name(self, field_name):
|
|
102
|
+
"""Get JSON field definition for field matching provided name or key
|
|
103
|
+
|
|
104
|
+
.. versionchanged:: 4.1.0
|
|
105
|
+
Added support for field keys
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
field_name (str): Target field name or key to get definition for
|
|
109
|
+
|
|
110
|
+
Raises:
|
|
111
|
+
swimlane.exceptions.UnknownField: Raised when given a field name not found in App
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
dict: Field metadata definition
|
|
115
|
+
"""
|
|
116
|
+
try:
|
|
117
|
+
return self._fields_by_name[self.resolve_field_name(field_name)]
|
|
118
|
+
except KeyError:
|
|
119
|
+
raise UnknownField(self, field_name, self._fields_by_name.keys())
|
|
120
|
+
|
|
121
|
+
def get_field_definition_by_id(self, field_id):
|
|
122
|
+
"""Get JSON field definition for field matching provided id
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
field_id (str): Target field ID to get definition for
|
|
126
|
+
|
|
127
|
+
Raises:
|
|
128
|
+
swimlane.exceptions.UnknownField: Raised when given a field ID not found in App
|
|
129
|
+
|
|
130
|
+
Returns:
|
|
131
|
+
dict: Field metadata definition
|
|
132
|
+
"""
|
|
133
|
+
try:
|
|
134
|
+
return self._fields_by_id[field_id]
|
|
135
|
+
except KeyError:
|
|
136
|
+
raise UnknownField(self, field_id, self._fields_by_id.keys())
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from swimlane.core.resources.app import App
|
|
2
|
+
from swimlane.core.resources.revision_base import RevisionBase
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class AppRevision(RevisionBase):
|
|
6
|
+
"""
|
|
7
|
+
Encapsulates a single revision returned from a History lookup.
|
|
8
|
+
|
|
9
|
+
Attributes:
|
|
10
|
+
Attributes:
|
|
11
|
+
modified_date: The date this app revision was created.
|
|
12
|
+
revision_number: The revision number of this app revision.
|
|
13
|
+
status: Indicates whether this revision is the current revision or a historical revision.
|
|
14
|
+
user: The user that saved this revision of the record.
|
|
15
|
+
version: The App corresponding to the data contained in this app revision.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
# Separator for unique ids. Unlikely to be found in application ids. Although technically we do not currently
|
|
19
|
+
# validate app ids in the backend for specific characters so this sequence could be found.
|
|
20
|
+
SEPARATOR = ' --- '
|
|
21
|
+
|
|
22
|
+
@staticmethod
|
|
23
|
+
def get_unique_id(app_id, revision_number):
|
|
24
|
+
"""Returns the unique identifier for the given AppRevision."""
|
|
25
|
+
return '{0}{1}{2}'.format(app_id, AppRevision.SEPARATOR, revision_number)
|
|
26
|
+
|
|
27
|
+
@staticmethod
|
|
28
|
+
def parse_unique_id(unique_id):
|
|
29
|
+
"""Returns an array containing two items: the app_id and revision number parsed from the given unique_id."""
|
|
30
|
+
return unique_id.split(AppRevision.SEPARATOR)
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def version(self):
|
|
34
|
+
"""Returns an App from the _raw_version info in this app revision. Lazy loaded. Overridden from base class."""
|
|
35
|
+
if not self._version:
|
|
36
|
+
self._version = App(self._swimlane, self._raw_version)
|
|
37
|
+
return self._version
|
|
38
|
+
|
|
39
|
+
def get_cache_index_keys(self):
|
|
40
|
+
"""Returns cache index keys for this AppRevision."""
|
|
41
|
+
return {
|
|
42
|
+
'app_id_revision': self.get_unique_id(self.version.id, self.revision_number)
|
|
43
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
from io import BytesIO
|
|
2
|
+
|
|
3
|
+
import pendulum
|
|
4
|
+
|
|
5
|
+
from swimlane.core.resources.base import APIResource
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Attachment(APIResource):
|
|
9
|
+
"""Abstraction of an attachment from an AttachmentsField
|
|
10
|
+
|
|
11
|
+
Attributes:
|
|
12
|
+
file_id (str): Full file ID used in download request URL
|
|
13
|
+
filename (str): Attachment filename
|
|
14
|
+
upload_date (pendulum.DateTime): Pendulum datetime when attachment was uploaded
|
|
15
|
+
record_id (str): Associated record ID used in download request URL
|
|
16
|
+
field_id (str): Assocated field ID used in download request URL
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
_type = 'Core.Models.Record.Attachment, Core'
|
|
20
|
+
|
|
21
|
+
def __init__(self, swimlane, raw, record_id, field_id):
|
|
22
|
+
super(Attachment, self).__init__(swimlane, raw)
|
|
23
|
+
|
|
24
|
+
self.file_id = self._raw['fileId']
|
|
25
|
+
self.filename = self._raw['filename']
|
|
26
|
+
self.upload_date = pendulum.parse(self._raw['uploadDate'])
|
|
27
|
+
self.record_id = record_id
|
|
28
|
+
self.field_id = field_id
|
|
29
|
+
|
|
30
|
+
def __str__(self):
|
|
31
|
+
return str(self.filename)
|
|
32
|
+
|
|
33
|
+
def __hash__(self):
|
|
34
|
+
return hash(self.file_id)
|
|
35
|
+
|
|
36
|
+
def download(self, chunk_size=1024):
|
|
37
|
+
"""Download attachment
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
chunk_size (int): Byte-size of chunked download request stream
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
BytesIO: Stream ready for reading containing the attachment file contents
|
|
44
|
+
"""
|
|
45
|
+
stream = BytesIO()
|
|
46
|
+
|
|
47
|
+
response = self._swimlane.request(
|
|
48
|
+
'get',
|
|
49
|
+
'attachment/{}/{}/{}'.format(self.record_id, self.field_id, self.file_id),
|
|
50
|
+
stream=True
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
for chunk in response.iter_content(chunk_size):
|
|
54
|
+
stream.write(chunk)
|
|
55
|
+
|
|
56
|
+
stream.seek(0)
|
|
57
|
+
|
|
58
|
+
return stream
|
|
59
|
+
|
|
60
|
+
def for_json(self):
|
|
61
|
+
"""Return metadata for JSON-compatible representation"""
|
|
62
|
+
return_value = self._raw.copy()
|
|
63
|
+
return_value.pop('$type')
|
|
64
|
+
return return_value
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import six
|
|
2
|
+
|
|
3
|
+
from swimlane.core.resolver import SwimlaneResolver
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class APIResourceMetaclass(type):
|
|
7
|
+
"""Metaclass for all APIResource classes"""
|
|
8
|
+
|
|
9
|
+
def __call__(cls, *args, **kwargs):
|
|
10
|
+
"""Hook __init__ call to push resource instance into Swimlane client ResourceCache after instantiation"""
|
|
11
|
+
resource_instance = type.__call__(cls, *args, **kwargs)
|
|
12
|
+
|
|
13
|
+
resource_instance._swimlane.resources_cache.cache(resource_instance)
|
|
14
|
+
|
|
15
|
+
return resource_instance
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class APIResource(six.with_metaclass(APIResourceMetaclass, SwimlaneResolver)):
|
|
19
|
+
"""Base class for all API resources with an associated $type and/or raw data"""
|
|
20
|
+
|
|
21
|
+
_type = None
|
|
22
|
+
|
|
23
|
+
def __init__(self, swimlane, raw):
|
|
24
|
+
super(APIResource, self).__init__(swimlane)
|
|
25
|
+
self._raw = raw
|
|
26
|
+
|
|
27
|
+
raw_type = self._raw.get('$type')
|
|
28
|
+
if self._type and raw_type != self._type:
|
|
29
|
+
raise TypeError('Expected $type = "{}", received "{}"'.format(self._type, raw_type))
|
|
30
|
+
|
|
31
|
+
def __repr__(self):
|
|
32
|
+
return '<{self.__class__.__name__}: {self!s}>'.format(self=self)
|
|
33
|
+
|
|
34
|
+
def __str__(self):
|
|
35
|
+
return ''
|
|
36
|
+
|
|
37
|
+
def __hash__(self):
|
|
38
|
+
"""Added for py2+3 compat"""
|
|
39
|
+
return int(id(self) / 16)
|
|
40
|
+
|
|
41
|
+
def __eq__(self, other):
|
|
42
|
+
"""Determine if an APIResource is of the same type and has the same hash value"""
|
|
43
|
+
return isinstance(other, self.__class__) and hash(self) == hash(other)
|
|
44
|
+
|
|
45
|
+
def __ne__(self, other):
|
|
46
|
+
# Default __ne__ for python 2 compat
|
|
47
|
+
return not self == other
|
|
48
|
+
|
|
49
|
+
def get_cache_internal_key(self):
|
|
50
|
+
"""Return real internal cache key for resource instance"""
|
|
51
|
+
return hash(self)
|
|
52
|
+
|
|
53
|
+
def get_cache_index_keys(self):
|
|
54
|
+
"""Return dict of key/value pairs used by ResourceCache to map resource values to internal cache instance"""
|
|
55
|
+
raise NotImplementedError
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import pendulum
|
|
2
|
+
|
|
3
|
+
from swimlane.core.resources.base import APIResource
|
|
4
|
+
from swimlane.core.resources.usergroup import UserGroup
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Comment(APIResource):
|
|
8
|
+
"""Abstraction of a single comment from a comment field
|
|
9
|
+
|
|
10
|
+
Attributes:
|
|
11
|
+
user (UserGroup): UserGroup instance of user who created the comment
|
|
12
|
+
created_date (pendulum.DateTime): Pendulum datetime of when comment was created
|
|
13
|
+
message (str): Comment message body
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
def __init__(self, swimlane, raw):
|
|
17
|
+
super(Comment, self).__init__(swimlane, raw)
|
|
18
|
+
|
|
19
|
+
self.user = UserGroup(swimlane, self._raw['createdByUser'])
|
|
20
|
+
self.created_date = pendulum.parse(self._raw['createdDate'])
|
|
21
|
+
self.message = self._raw['message']
|
|
22
|
+
self.is_rich_text = self._raw.get('isRichText', False)
|
|
23
|
+
|
|
24
|
+
def __str__(self):
|
|
25
|
+
return self.message
|
|
26
|
+
|
|
27
|
+
def for_json(self):
|
|
28
|
+
"""Called by CommentField.for_json(), returns relevant Comment attributes in JSON-compatible format"""
|
|
29
|
+
return {
|
|
30
|
+
'message': self.message,
|
|
31
|
+
'createdDate': self._raw['createdDate'],
|
|
32
|
+
'createdByUser': self.user.for_json()
|
|
33
|
+
}
|