sdata 1.5.0__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 (98) hide show
  1. sdata/__init__.py +140 -0
  2. sdata/base.py +684 -0
  3. sdata/contrib/__init__.py +0 -0
  4. sdata/contrib/piexif/__init__.py +12 -0
  5. sdata/contrib/piexif/_common.py +94 -0
  6. sdata/contrib/piexif/_dump.py +346 -0
  7. sdata/contrib/piexif/_exceptions.py +2 -0
  8. sdata/contrib/piexif/_exif.py +638 -0
  9. sdata/contrib/piexif/_insert.py +60 -0
  10. sdata/contrib/piexif/_load.py +270 -0
  11. sdata/contrib/piexif/_remove.py +56 -0
  12. sdata/contrib/piexif/_transplant.py +45 -0
  13. sdata/contrib/piexif/_webp.py +240 -0
  14. sdata/contrib/piexif/helper.py +66 -0
  15. sdata/contrib/ranger/__init__.py +8 -0
  16. sdata/contrib/ranger/collections/__init__.py +0 -0
  17. sdata/contrib/ranger/collections/rangebucketmap.py +346 -0
  18. sdata/contrib/ranger/collections/rangemap.py +396 -0
  19. sdata/contrib/ranger/collections/rangeset.py +483 -0
  20. sdata/contrib/ranger/range/__init__.py +0 -0
  21. sdata/contrib/ranger/range/cut.py +285 -0
  22. sdata/contrib/ranger/range/range.py +644 -0
  23. sdata/contrib/sobol_seq.py +394 -0
  24. sdata/contrib/sortedcontainers/LICENSE +13 -0
  25. sdata/contrib/sortedcontainers/__init__.py +74 -0
  26. sdata/contrib/sortedcontainers/sorteddict.py +789 -0
  27. sdata/contrib/sortedcontainers/sortedlist.py +2646 -0
  28. sdata/contrib/sortedcontainers/sortedset.py +733 -0
  29. sdata/contrib/sqlitedict.py +598 -0
  30. sdata/contrib/timeflake/__init__.py +40 -0
  31. sdata/contrib/timeflake/flake.py +69 -0
  32. sdata/contrib/timeflake/utils.py +47 -0
  33. sdata/deprecated/__init__.py +10 -0
  34. sdata/deprecated/data.py +1503 -0
  35. sdata/did/__init__.py +129 -0
  36. sdata/did/_http.py +67 -0
  37. sdata/did/base58btc.py +49 -0
  38. sdata/did/did_github.py +152 -0
  39. sdata/did/did_peer4.py +155 -0
  40. sdata/did/did_web.py +222 -0
  41. sdata/did/diddoc.py +75 -0
  42. sdata/did/ed25519.py +225 -0
  43. sdata/did/eddsa.py +107 -0
  44. sdata/did/errors.py +40 -0
  45. sdata/did/issue_vc.py +193 -0
  46. sdata/did/jose.py +126 -0
  47. sdata/did/keys.py +104 -0
  48. sdata/did/utils_didkey.py +161 -0
  49. sdata/did/verify_vp.py +158 -0
  50. sdata/doe.py +101 -0
  51. sdata/dtypes.py +510 -0
  52. sdata/experiments/__init__.py +331 -0
  53. sdata/experiments/blob0.py +131 -0
  54. sdata/experiments/file.py +42 -0
  55. sdata/experiments/filegroup.py +144 -0
  56. sdata/experiments/image_old.py +313 -0
  57. sdata/experiments/ks2.py +15 -0
  58. sdata/experiments/lapshear.py +8 -0
  59. sdata/experiments/material.py +7 -0
  60. sdata/experiments/parameters_deprecated.py +394 -0
  61. sdata/experiments/workbook.py +226 -0
  62. sdata/format.py +125 -0
  63. sdata/imagemeta.py +533 -0
  64. sdata/interactive.py +118 -0
  65. sdata/iolib/__init__.py +51 -0
  66. sdata/iolib/hashtree.py +264 -0
  67. sdata/iolib/json1sqlitestore.py +554 -0
  68. sdata/iolib/jsonsqlitestore.py +704 -0
  69. sdata/iolib/pud.py +242 -0
  70. sdata/iolib/reader.py +262 -0
  71. sdata/iolib/writer.py +419 -0
  72. sdata/metadata.py +882 -0
  73. sdata/parameter.py +414 -0
  74. sdata/schema.py +273 -0
  75. sdata/sclass/__init__.py +111 -0
  76. sdata/sclass/bfo_classes.py +362 -0
  77. sdata/sclass/blob.py +362 -0
  78. sdata/sclass/content.py +87 -0
  79. sdata/sclass/dataframe.py +1251 -0
  80. sdata/sclass/dataframegroup.py +187 -0
  81. sdata/sclass/dependency_graph.py +85 -0
  82. sdata/sclass/filereference.py +124 -0
  83. sdata/sclass/image.py +218 -0
  84. sdata/sclass/lazy_namespace.py +155 -0
  85. sdata/sclass/process.py +383 -0
  86. sdata/sclass/process_graph.py +221 -0
  87. sdata/sclass/zipfile.py +135 -0
  88. sdata/semantic.py +346 -0
  89. sdata/suuid.py +136 -0
  90. sdata/timestamp.py +336 -0
  91. sdata/tools.py +14 -0
  92. sdata/units.py +602 -0
  93. sdata/vocab.py +149 -0
  94. sdata-1.5.0.dist-info/METADATA +264 -0
  95. sdata-1.5.0.dist-info/RECORD +98 -0
  96. sdata-1.5.0.dist-info/WHEEL +5 -0
  97. sdata-1.5.0.dist-info/licenses/LICENSE-MIT +21 -0
  98. sdata-1.5.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,94 @@
1
+ import struct
2
+
3
+ from ._exceptions import InvalidImageDataError
4
+
5
+
6
+ def split_into_segments(data):
7
+ """Slices JPEG meta data into a list from JPEG binary data.
8
+ """
9
+ if data[0:2] != b"\xff\xd8":
10
+ raise InvalidImageDataError("Given data isn't JPEG.")
11
+
12
+ head = 2
13
+ segments = [b"\xff\xd8"]
14
+ while 1:
15
+ if data[head: head + 2] == b"\xff\xda":
16
+ segments.append(data[head:])
17
+ break
18
+ else:
19
+ length = struct.unpack(">H", data[head + 2: head + 4])[0]
20
+ endPoint = head + length + 2
21
+ seg = data[head: endPoint]
22
+ segments.append(seg)
23
+ head = endPoint
24
+
25
+ if (head >= len(data)):
26
+ raise InvalidImageDataError("Wrong JPEG data.")
27
+ return segments
28
+
29
+ def read_exif_from_file(filename):
30
+ """Slices JPEG meta data into a list from JPEG binary data.
31
+ """
32
+ f = open(filename, "rb")
33
+ data = f.read(6)
34
+
35
+ if data[0:2] != b"\xff\xd8":
36
+ raise InvalidImageDataError("Given data isn't JPEG.")
37
+
38
+ head = data[2:6]
39
+ HEAD_LENGTH = 4
40
+ exif = None
41
+ while len(head) == HEAD_LENGTH:
42
+ length = struct.unpack(">H", head[2: 4])[0]
43
+
44
+ if head[:2] == b"\xff\xe1":
45
+ segment_data = f.read(length - 2)
46
+ if segment_data[:4] != b'Exif':
47
+ head = f.read(HEAD_LENGTH)
48
+ continue
49
+ exif = head + segment_data
50
+ break
51
+ elif head[0:1] == b"\xff":
52
+ f.read(length - 2)
53
+ head = f.read(HEAD_LENGTH)
54
+ else:
55
+ break
56
+
57
+ f.close()
58
+ return exif
59
+
60
+ def get_exif_seg(segments):
61
+ """Returns Exif from JPEG meta data list
62
+ """
63
+ for seg in segments:
64
+ if seg[0:2] == b"\xff\xe1" and seg[4:10] == b"Exif\x00\x00":
65
+ return seg
66
+ return None
67
+
68
+
69
+ def merge_segments(segments, exif=b""):
70
+ """Merges Exif with APP0 and APP1 manipulations.
71
+ """
72
+ if segments[1][0:2] == b"\xff\xe0" and \
73
+ segments[2][0:2] == b"\xff\xe1" and \
74
+ segments[2][4:10] == b"Exif\x00\x00":
75
+ if exif:
76
+ segments[2] = exif
77
+ segments.pop(1)
78
+ elif exif is None:
79
+ segments.pop(2)
80
+ else:
81
+ segments.pop(1)
82
+ elif segments[1][0:2] == b"\xff\xe0":
83
+ if exif:
84
+ segments[1] = exif
85
+ elif segments[1][0:2] == b"\xff\xe1" and \
86
+ segments[1][4:10] == b"Exif\x00\x00":
87
+ if exif:
88
+ segments[1] = exif
89
+ elif exif is None:
90
+ segments.pop(1)
91
+ else:
92
+ if exif:
93
+ segments.insert(1, exif)
94
+ return b"".join(segments)
@@ -0,0 +1,346 @@
1
+ import copy
2
+ import numbers
3
+ import struct
4
+
5
+ from ._common import *
6
+ from ._exif import *
7
+
8
+
9
+ TIFF_HEADER_LENGTH = 8
10
+
11
+
12
+ def dump(exif_dict_original):
13
+ """
14
+ py:function:: piexif.load(data)
15
+
16
+ Return exif as bytes.
17
+
18
+ :param dict exif: Exif data({"0th":dict, "Exif":dict, "GPS":dict, "Interop":dict, "1st":dict, "thumbnail":bytes})
19
+ :return: Exif
20
+ :rtype: bytes
21
+ """
22
+ exif_dict = copy.deepcopy(exif_dict_original)
23
+ header = b"Exif\x00\x00\x4d\x4d\x00\x2a\x00\x00\x00\x08"
24
+ exif_is = False
25
+ gps_is = False
26
+ interop_is = False
27
+ first_is = False
28
+
29
+ if "0th" in exif_dict:
30
+ zeroth_ifd = exif_dict["0th"]
31
+ else:
32
+ zeroth_ifd = {}
33
+
34
+ if (("Exif" in exif_dict) and len(exif_dict["Exif"]) or
35
+ ("Interop" in exif_dict) and len(exif_dict["Interop"]) ):
36
+ zeroth_ifd[ImageIFD.ExifTag] = 1
37
+ exif_is = True
38
+ exif_ifd = exif_dict["Exif"]
39
+ if ("Interop" in exif_dict) and len(exif_dict["Interop"]):
40
+ exif_ifd[ExifIFD. InteroperabilityTag] = 1
41
+ interop_is = True
42
+ interop_ifd = exif_dict["Interop"]
43
+ elif ExifIFD. InteroperabilityTag in exif_ifd:
44
+ exif_ifd.pop(ExifIFD.InteroperabilityTag)
45
+ elif ImageIFD.ExifTag in zeroth_ifd:
46
+ zeroth_ifd.pop(ImageIFD.ExifTag)
47
+
48
+ if ("GPS" in exif_dict) and len(exif_dict["GPS"]):
49
+ zeroth_ifd[ImageIFD.GPSTag] = 1
50
+ gps_is = True
51
+ gps_ifd = exif_dict["GPS"]
52
+ elif ImageIFD.GPSTag in zeroth_ifd:
53
+ zeroth_ifd.pop(ImageIFD.GPSTag)
54
+
55
+ if (("1st" in exif_dict) and
56
+ ("thumbnail" in exif_dict) and
57
+ (exif_dict["thumbnail"] is not None)):
58
+ first_is = True
59
+ exif_dict["1st"][ImageIFD.JPEGInterchangeFormat] = 1
60
+ exif_dict["1st"][ImageIFD.JPEGInterchangeFormatLength] = 1
61
+ first_ifd = exif_dict["1st"]
62
+
63
+ zeroth_set = _dict_to_bytes(zeroth_ifd, "0th", 0)
64
+ zeroth_length = (len(zeroth_set[0]) + exif_is * 12 + gps_is * 12 + 4 +
65
+ len(zeroth_set[1]))
66
+
67
+ if exif_is:
68
+ exif_set = _dict_to_bytes(exif_ifd, "Exif", zeroth_length)
69
+ exif_length = len(exif_set[0]) + interop_is * 12 + len(exif_set[1])
70
+ else:
71
+ exif_bytes = b""
72
+ exif_length = 0
73
+ if gps_is:
74
+ gps_set = _dict_to_bytes(gps_ifd, "GPS", zeroth_length + exif_length)
75
+ gps_bytes = b"".join(gps_set)
76
+ gps_length = len(gps_bytes)
77
+ else:
78
+ gps_bytes = b""
79
+ gps_length = 0
80
+ if interop_is:
81
+ offset = zeroth_length + exif_length + gps_length
82
+ interop_set = _dict_to_bytes(interop_ifd, "Interop", offset)
83
+ interop_bytes = b"".join(interop_set)
84
+ interop_length = len(interop_bytes)
85
+ else:
86
+ interop_bytes = b""
87
+ interop_length = 0
88
+ if first_is:
89
+ offset = zeroth_length + exif_length + gps_length + interop_length
90
+ first_set = _dict_to_bytes(first_ifd, "1st", offset)
91
+ thumbnail = _get_thumbnail(exif_dict["thumbnail"])
92
+ thumbnail_max_size = 64000
93
+ if len(thumbnail) > thumbnail_max_size:
94
+ raise ValueError("Given thumbnail is too large. max 64kB")
95
+ else:
96
+ first_bytes = b""
97
+ if exif_is:
98
+ pointer_value = TIFF_HEADER_LENGTH + zeroth_length
99
+ pointer_str = struct.pack(">I", pointer_value)
100
+ key = ImageIFD.ExifTag
101
+ key_str = struct.pack(">H", key)
102
+ type_str = struct.pack(">H", TYPES.Long)
103
+ length_str = struct.pack(">I", 1)
104
+ exif_pointer = key_str + type_str + length_str + pointer_str
105
+ else:
106
+ exif_pointer = b""
107
+ if gps_is:
108
+ pointer_value = TIFF_HEADER_LENGTH + zeroth_length + exif_length
109
+ pointer_str = struct.pack(">I", pointer_value)
110
+ key = ImageIFD.GPSTag
111
+ key_str = struct.pack(">H", key)
112
+ type_str = struct.pack(">H", TYPES.Long)
113
+ length_str = struct.pack(">I", 1)
114
+ gps_pointer = key_str + type_str + length_str + pointer_str
115
+ else:
116
+ gps_pointer = b""
117
+ if interop_is:
118
+ pointer_value = (TIFF_HEADER_LENGTH +
119
+ zeroth_length + exif_length + gps_length)
120
+ pointer_str = struct.pack(">I", pointer_value)
121
+ key = ExifIFD.InteroperabilityTag
122
+ key_str = struct.pack(">H", key)
123
+ type_str = struct.pack(">H", TYPES.Long)
124
+ length_str = struct.pack(">I", 1)
125
+ interop_pointer = key_str + type_str + length_str + pointer_str
126
+ else:
127
+ interop_pointer = b""
128
+ if first_is:
129
+ pointer_value = (TIFF_HEADER_LENGTH + zeroth_length +
130
+ exif_length + gps_length + interop_length)
131
+ first_ifd_pointer = struct.pack(">L", pointer_value)
132
+ thumbnail_pointer = (pointer_value + len(first_set[0]) + 24 +
133
+ 4 + len(first_set[1]))
134
+ thumbnail_p_bytes = (b"\x02\x01\x00\x04\x00\x00\x00\x01" +
135
+ struct.pack(">L", thumbnail_pointer))
136
+ thumbnail_length_bytes = (b"\x02\x02\x00\x04\x00\x00\x00\x01" +
137
+ struct.pack(">L", len(thumbnail)))
138
+ first_bytes = (first_set[0] + thumbnail_p_bytes +
139
+ thumbnail_length_bytes + b"\x00\x00\x00\x00" +
140
+ first_set[1] + thumbnail)
141
+ else:
142
+ first_ifd_pointer = b"\x00\x00\x00\x00"
143
+
144
+ zeroth_bytes = (zeroth_set[0] + exif_pointer + gps_pointer +
145
+ first_ifd_pointer + zeroth_set[1])
146
+ if exif_is:
147
+ exif_bytes = exif_set[0] + interop_pointer + exif_set[1]
148
+
149
+ return (header + zeroth_bytes + exif_bytes + gps_bytes +
150
+ interop_bytes + first_bytes)
151
+
152
+
153
+ def _get_thumbnail(jpeg):
154
+ segments = split_into_segments(jpeg)
155
+ while (b"\xff\xe0" <= segments[1][0:2] <= b"\xff\xef"):
156
+ segments.pop(1)
157
+ thumbnail = b"".join(segments)
158
+ return thumbnail
159
+
160
+
161
+ def _pack_byte(*args):
162
+ return struct.pack("B" * len(args), *args)
163
+
164
+ def _pack_signed_byte(*args):
165
+ return struct.pack("b" * len(args), *args)
166
+
167
+ def _pack_short(*args):
168
+ return struct.pack(">" + "H" * len(args), *args)
169
+
170
+ def _pack_signed_short(*args):
171
+ return struct.pack(">" + "h" * len(args), *args)
172
+
173
+ def _pack_long(*args):
174
+ return struct.pack(">" + "L" * len(args), *args)
175
+
176
+ def _pack_slong(*args):
177
+ return struct.pack(">" + "l" * len(args), *args)
178
+
179
+ def _pack_float(*args):
180
+ return struct.pack(">" + "f" * len(args), *args)
181
+
182
+ def _pack_double(*args):
183
+ return struct.pack(">" + "d" * len(args), *args)
184
+
185
+
186
+ def _value_to_bytes(raw_value, value_type, offset):
187
+ four_bytes_over = b""
188
+ value_str = b""
189
+
190
+ if value_type == TYPES.Byte:
191
+ length = len(raw_value)
192
+ if length <= 4:
193
+ value_str = (_pack_byte(*raw_value) +
194
+ b"\x00" * (4 - length))
195
+ else:
196
+ value_str = struct.pack(">I", offset)
197
+ four_bytes_over = _pack_byte(*raw_value)
198
+ elif value_type == TYPES.Short:
199
+ length = len(raw_value)
200
+ if length <= 2:
201
+ value_str = (_pack_short(*raw_value) +
202
+ b"\x00\x00" * (2 - length))
203
+ else:
204
+ value_str = struct.pack(">I", offset)
205
+ four_bytes_over = _pack_short(*raw_value)
206
+ elif value_type == TYPES.Long:
207
+ length = len(raw_value)
208
+ if length <= 1:
209
+ value_str = _pack_long(*raw_value)
210
+ else:
211
+ value_str = struct.pack(">I", offset)
212
+ four_bytes_over = _pack_long(*raw_value)
213
+ elif value_type == TYPES.SLong:
214
+ length = len(raw_value)
215
+ if length <= 1:
216
+ value_str = _pack_slong(*raw_value)
217
+ else:
218
+ value_str = struct.pack(">I", offset)
219
+ four_bytes_over = _pack_slong(*raw_value)
220
+ elif value_type == TYPES.Ascii:
221
+ try:
222
+ new_value = raw_value.encode("latin1") + b"\x00"
223
+ except:
224
+ try:
225
+ new_value = raw_value + b"\x00"
226
+ except TypeError:
227
+ raise ValueError("Got invalid type to convert.")
228
+ length = len(new_value)
229
+ if length > 4:
230
+ value_str = struct.pack(">I", offset)
231
+ four_bytes_over = new_value
232
+ else:
233
+ value_str = new_value + b"\x00" * (4 - length)
234
+ elif value_type == TYPES.Rational:
235
+ if isinstance(raw_value[0], numbers.Integral):
236
+ length = 1
237
+ num, den = raw_value
238
+ new_value = struct.pack(">L", num) + struct.pack(">L", den)
239
+ elif isinstance(raw_value[0], tuple):
240
+ length = len(raw_value)
241
+ new_value = b""
242
+ for n, val in enumerate(raw_value):
243
+ num, den = val
244
+ new_value += (struct.pack(">L", num) +
245
+ struct.pack(">L", den))
246
+ value_str = struct.pack(">I", offset)
247
+ four_bytes_over = new_value
248
+ elif value_type == TYPES.SRational:
249
+ if isinstance(raw_value[0], numbers.Integral):
250
+ length = 1
251
+ num, den = raw_value
252
+ new_value = struct.pack(">l", num) + struct.pack(">l", den)
253
+ elif isinstance(raw_value[0], tuple):
254
+ length = len(raw_value)
255
+ new_value = b""
256
+ for n, val in enumerate(raw_value):
257
+ num, den = val
258
+ new_value += (struct.pack(">l", num) +
259
+ struct.pack(">l", den))
260
+ value_str = struct.pack(">I", offset)
261
+ four_bytes_over = new_value
262
+ elif value_type == TYPES.Undefined:
263
+ length = len(raw_value)
264
+ if length > 4:
265
+ value_str = struct.pack(">I", offset)
266
+ try:
267
+ four_bytes_over = b"" + raw_value
268
+ except TypeError:
269
+ raise ValueError("Got invalid type to convert.")
270
+ else:
271
+ try:
272
+ value_str = raw_value + b"\x00" * (4 - length)
273
+ except TypeError:
274
+ raise ValueError("Got invalid type to convert.")
275
+ elif value_type == TYPES.SByte: # Signed Byte
276
+ length = len(raw_value)
277
+ if length <= 4:
278
+ value_str = (_pack_signed_byte(*raw_value) +
279
+ b"\x00" * (4 - length))
280
+ else:
281
+ value_str = struct.pack(">I", offset)
282
+ four_bytes_over = _pack_signed_byte(*raw_value)
283
+ elif value_type == TYPES.SShort: # Signed Short
284
+ length = len(raw_value)
285
+ if length <= 2:
286
+ value_str = (_pack_signed_short(*raw_value) +
287
+ b"\x00\x00" * (2 - length))
288
+ else:
289
+ value_str = struct.pack(">I", offset)
290
+ four_bytes_over = _pack_signed_short(*raw_value)
291
+ elif value_type == TYPES.Float:
292
+ length = len(raw_value)
293
+ if length <= 1:
294
+ value_str = _pack_float(*raw_value)
295
+ else:
296
+ value_str = struct.pack(">I", offset)
297
+ four_bytes_over = _pack_float(*raw_value)
298
+ elif value_type == TYPES.DFloat: # Double
299
+ length = len(raw_value)
300
+ value_str = struct.pack(">I", offset)
301
+ four_bytes_over = _pack_double(*raw_value)
302
+
303
+ length_str = struct.pack(">I", length)
304
+ return length_str, value_str, four_bytes_over
305
+
306
+ def _dict_to_bytes(ifd_dict, ifd, ifd_offset):
307
+ tag_count = len(ifd_dict)
308
+ entry_header = struct.pack(">H", tag_count)
309
+ if ifd in ("0th", "1st"):
310
+ entries_length = 2 + tag_count * 12 + 4
311
+ else:
312
+ entries_length = 2 + tag_count * 12
313
+ entries = b""
314
+ values = b""
315
+
316
+ for n, key in enumerate(sorted(ifd_dict)):
317
+ if (ifd == "0th") and (key in (ImageIFD.ExifTag, ImageIFD.GPSTag)):
318
+ continue
319
+ elif (ifd == "Exif") and (key == ExifIFD.InteroperabilityTag):
320
+ continue
321
+ elif (ifd == "1st") and (key in (ImageIFD.JPEGInterchangeFormat, ImageIFD.JPEGInterchangeFormatLength)):
322
+ continue
323
+
324
+ raw_value = ifd_dict[key]
325
+ key_str = struct.pack(">H", key)
326
+ value_type = TAGS[ifd][key]["type"]
327
+ type_str = struct.pack(">H", value_type)
328
+ four_bytes_over = b""
329
+
330
+ if isinstance(raw_value, numbers.Integral) or isinstance(raw_value, float):
331
+ raw_value = (raw_value,)
332
+ offset = TIFF_HEADER_LENGTH + entries_length + ifd_offset + len(values)
333
+
334
+ try:
335
+ length_str, value_str, four_bytes_over = _value_to_bytes(raw_value,
336
+ value_type,
337
+ offset)
338
+ except ValueError:
339
+ raise ValueError(
340
+ '"dump" got wrong type of exif value.\n' +
341
+ '{} in {} IFD. Got as {}.'.format(key, ifd, type(ifd_dict[key]))
342
+ )
343
+
344
+ entries += key_str + type_str + length_str + value_str
345
+ values += four_bytes_over
346
+ return (entry_header + entries, values)
@@ -0,0 +1,2 @@
1
+ class InvalidImageDataError(ValueError):
2
+ pass