structtype 0.3.0__cp313-cp313-win_amd64.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.
structtype/_utils.py ADDED
@@ -0,0 +1,410 @@
1
+ # type: ignore
2
+ import collections
3
+ import sys
4
+ import types
5
+ import typing
6
+ from typing import Any, _AnnotatedAlias, _GenericAlias # noqa: F401
7
+
8
+ try:
9
+ from typing_extensions import get_type_hints as _get_type_hints
10
+ except Exception:
11
+ from typing import get_type_hints as _get_type_hints
12
+
13
+ try:
14
+ from typing_extensions import NotRequired, Required
15
+ except Exception:
16
+ try:
17
+ from typing import NotRequired, Required
18
+ except Exception:
19
+ Required = NotRequired = None
20
+
21
+
22
+ def get_type_hints(obj):
23
+ return _get_type_hints(obj, include_extras=True)
24
+
25
+
26
+ PY_312PLUS = sys.version_info >= (3, 12)
27
+
28
+ # The `is_class` argument was new in 3.11, but was backported to 3.9 and 3.10.
29
+ # It's _likely_ to be available for 3.9/3.10, but may not be. Easiest way to
30
+ # check is to try it and see. This check can be removed when we drop support
31
+ # for Python 3.10.
32
+ try:
33
+ typing.ForwardRef("Foo", is_class=True)
34
+ except TypeError:
35
+
36
+ def _forward_ref(value):
37
+ return typing.ForwardRef(value, is_argument=False)
38
+
39
+ else:
40
+
41
+ def _forward_ref(value):
42
+ return typing.ForwardRef(value, is_argument=False, is_class=True)
43
+
44
+
45
+ # Python 3.13 adds a new mandatory type_params kwarg to _eval_type
46
+ if sys.version_info >= (3, 13):
47
+
48
+ def _eval_type(t, globalns, localns):
49
+ return typing._eval_type(t, globalns, localns, ())
50
+
51
+ else:
52
+ _eval_type = typing._eval_type
53
+
54
+
55
+ if sys.version_info >= (3, 14):
56
+ from annotationlib import get_annotations as _get_class_annotations
57
+ else:
58
+
59
+ def _get_class_annotations(cls):
60
+ return cls.__dict__.get("__annotations__", {})
61
+
62
+
63
+ def _apply_params(obj, mapping):
64
+ if isinstance(obj, typing.TypeVar):
65
+ return mapping.get(obj, obj)
66
+
67
+ try:
68
+ parameters = tuple(obj.__parameters__)
69
+ except Exception:
70
+ # Not parameterized or __parameters__ is invalid, ignore
71
+ return obj
72
+
73
+ if not parameters:
74
+ # Not parametrized
75
+ return obj
76
+
77
+ # Parametrized
78
+ args = tuple(mapping.get(p, p) for p in parameters)
79
+ return obj[args]
80
+
81
+
82
+ def _get_class_mro_and_typevar_mappings(obj):
83
+ mapping = {}
84
+
85
+ # in Python 3.10 a natively produced 'types.GenericAlias' (e.g. 'list[int]', or the
86
+ # 'Base[int]' produced when a 'Generic' subclass inherits a builtin's
87
+ # '__class_getitem__') satisfies 'isinstance(_, type)', unlike on 3.11+. we still
88
+ # want to treat those as parametrised aliases, not bare classes
89
+ if isinstance(obj, type) and not isinstance(obj, types.GenericAlias):
90
+ cls = obj
91
+ else:
92
+ cls = obj.__origin__
93
+
94
+ def inner(c, scope):
95
+ if isinstance(c, type) and not isinstance(c, types.GenericAlias):
96
+ cls = c
97
+ new_scope = {}
98
+ else:
99
+ cls = typing.get_origin(c)
100
+ if cls in (None, object, typing.Generic) or cls in mapping:
101
+ return
102
+
103
+ if hasattr(cls, "__parameters__"):
104
+ # 'cls' carries its own type vars. This covers both ordinary
105
+ # 'typing._GenericAlias' bases and the 'types.GenericAlias' that
106
+ # get produced when a user-defined 'Generic' subclass inherits a
107
+ # builtin's '__class_getitem__' (e.g. 'class Base(Mapping[str, T])',
108
+ # whose 'Base[...]' is a 'types.GenericAlias'). Map cls's own
109
+ # type vars onto the resolved args, applying '_apply_params' so any
110
+ # outer bindings in 'scope' (e.g. 'U -> int') are propagated.
111
+ params = cls.__parameters__
112
+ args = tuple(_apply_params(a, scope) for a in typing.get_args(c))
113
+ assert len(params) == len(args)
114
+ new_scope = dict(zip(params, args))
115
+ else:
116
+ # a true built-in generic (e.g. 'collections.abc.Mapping[str, T]')
117
+ # whose '__origin__' has no '__parameters__'; the unresolved type
118
+ # vars and args live on the alias itself, not the origin.
119
+ new_scope = dict(zip(c.__parameters__, typing.get_args(c)))
120
+ mapping[cls] = new_scope
121
+
122
+ if issubclass(cls, typing.Generic):
123
+ bases = getattr(cls, "__orig_bases__", cls.__bases__)
124
+ for b in bases:
125
+ inner(b, new_scope)
126
+
127
+ inner(obj, {})
128
+ return cls.__mro__, mapping
129
+
130
+
131
+ def get_class_annotations(obj):
132
+ """Get the annotations for a class.
133
+
134
+ This is similar to ``typing.get_type_hints``, except:
135
+
136
+ - We maintain it
137
+ - It leaves extras like ``Annotated``/``ClassVar`` alone
138
+ - It resolves any parametrized generics in the class mro. The returned
139
+ mapping may still include ``TypeVar`` values, but those should be treated
140
+ as their unparametrized variants (i.e. equal to ``Any`` for the common case).
141
+
142
+ Note that this function doesn't check that Generic types are being used
143
+ properly - invalid uses of `Generic` may slip through without complaint.
144
+
145
+ The assumption here is that the user is making use of a static analysis
146
+ tool like ``mypy``/``pyright`` already, which would catch misuse of these
147
+ APIs.
148
+ """
149
+ hints = {}
150
+ mro, typevar_mappings = _get_class_mro_and_typevar_mappings(obj)
151
+
152
+ for cls in mro:
153
+ if cls in (typing.Generic, object):
154
+ continue
155
+
156
+ mapping = typevar_mappings.get(cls)
157
+ cls_locals = dict(vars(cls))
158
+
159
+ if PY_312PLUS:
160
+ # resolve type parameters (e.g. class Foo[T]: pass)
161
+ cls_locals.update({p.__name__: p for p in cls.__type_params__})
162
+
163
+ try:
164
+ cls_module = cls.__module__
165
+ except AttributeError:
166
+ cls_globals = {}
167
+ else:
168
+ cls_globals = getattr(sys.modules.get(cls_module, None), "__dict__", {})
169
+
170
+ ann = _get_class_annotations(cls)
171
+ for name, value in ann.items():
172
+ if name in hints:
173
+ continue
174
+ if isinstance(value, str):
175
+ value = _forward_ref(value)
176
+ value = _eval_type(value, cls_locals, cls_globals)
177
+ if mapping is not None:
178
+ value = _apply_params(value, mapping)
179
+ if value is None:
180
+ value = type(None)
181
+ hints[name] = value
182
+ return hints
183
+
184
+
185
+ # A mapping from a type annotation (or annotation __origin__) to the concrete
186
+ # python type that structtype will use when decoding. THIS IS PRIVATE FOR A
187
+ # REASON. DON'T MUCK WITH THIS.
188
+ _CONCRETE_TYPES = {
189
+ list: list,
190
+ tuple: tuple,
191
+ set: set,
192
+ frozenset: frozenset,
193
+ dict: dict,
194
+ typing.List: list,
195
+ typing.Tuple: tuple,
196
+ typing.Set: set,
197
+ typing.FrozenSet: frozenset,
198
+ typing.Dict: dict,
199
+ typing.Collection: list,
200
+ typing.MutableSequence: list,
201
+ typing.Sequence: list,
202
+ typing.MutableMapping: dict,
203
+ typing.Mapping: dict,
204
+ typing.MutableSet: set,
205
+ typing.AbstractSet: set,
206
+ collections.abc.Collection: list,
207
+ collections.abc.MutableSequence: list,
208
+ collections.abc.Sequence: list,
209
+ collections.abc.MutableSet: set,
210
+ collections.abc.Set: set,
211
+ collections.abc.MutableMapping: dict,
212
+ collections.abc.Mapping: dict,
213
+ }
214
+ if sys.version_info >= (3, 15):
215
+ _CONCRETE_TYPES.update({frozendict: frozendict}) # noqa: F821
216
+
217
+
218
+ def get_typeddict_info(obj):
219
+ if isinstance(obj, type):
220
+ cls = obj
221
+ else:
222
+ cls = obj.__origin__
223
+
224
+ raw_hints = get_class_annotations(obj)
225
+
226
+ if hasattr(cls, "__required_keys__"):
227
+ required = set(cls.__required_keys__)
228
+ elif cls.__total__:
229
+ required = set(raw_hints)
230
+ else:
231
+ required = set()
232
+
233
+ # Both `typing.TypedDict` and `typing_extensions.TypedDict` have a bug
234
+ # where `Required`/`NotRequired` aren't properly detected at runtime when
235
+ # `__future__.annotations` is enabled, meaning the `__required_keys__`
236
+ # isn't correct. This code block works around this issue by amending the
237
+ # set of required keys as needed, while also stripping off any
238
+ # `Required`/`NotRequired` wrappers.
239
+ hints = {}
240
+ for k, v in raw_hints.items():
241
+ origin = getattr(v, "__origin__", False)
242
+ if origin is Required:
243
+ required.add(k)
244
+ hints[k] = v.__args__[0]
245
+ elif origin is NotRequired:
246
+ required.discard(k)
247
+ hints[k] = v.__args__[0]
248
+ else:
249
+ hints[k] = v
250
+
251
+ # This can happen if there is a bug in the TypedDict implementation;
252
+ # such a bug was present in Python 3.14.
253
+ if not all(k in hints for k in required):
254
+ raise RuntimeError(
255
+ f"Required set {required} contains keys that are no in hints: {hints.keys()}"
256
+ )
257
+ return hints, required
258
+
259
+
260
+ def get_dataclass_info(obj):
261
+ if isinstance(obj, type):
262
+ cls = obj
263
+ else:
264
+ cls = obj.__origin__
265
+ hints = get_class_annotations(obj)
266
+ required = []
267
+ optional = []
268
+ defaults = []
269
+
270
+ if hasattr(cls, "__dataclass_fields__"):
271
+ from dataclasses import _FIELD, _FIELD_INITVAR, MISSING
272
+
273
+ for field in cls.__dataclass_fields__.values():
274
+ if field._field_type is not _FIELD:
275
+ if field._field_type is _FIELD_INITVAR:
276
+ raise TypeError(
277
+ "dataclasses with `InitVar` fields are not supported"
278
+ )
279
+ continue
280
+ name = field.name
281
+ typ = hints[name]
282
+ if field.default is not MISSING:
283
+ defaults.append(field.default)
284
+ optional.append((name, typ, False))
285
+ elif field.default_factory is not MISSING:
286
+ defaults.append(field.default_factory)
287
+ optional.append((name, typ, True))
288
+ else:
289
+ required.append((name, typ, False))
290
+
291
+ required.extend(optional)
292
+
293
+ pre_init = None
294
+ post_init = getattr(cls, "__post_init__", None)
295
+ else:
296
+ from attrs import NOTHING, Factory
297
+
298
+ fields_with_validators = []
299
+
300
+ for field in cls.__attrs_attrs__:
301
+ name = field.name
302
+ typ = hints[name]
303
+ default = field.default
304
+ if default is not NOTHING:
305
+ if isinstance(default, Factory):
306
+ if default.takes_self:
307
+ raise NotImplementedError(
308
+ "Support for default factories with `takes_self=True` "
309
+ "is not implemented. File a GitHub issue if you need "
310
+ "this feature!"
311
+ )
312
+ defaults.append(default.factory)
313
+ optional.append((name, typ, True))
314
+ else:
315
+ defaults.append(default)
316
+ optional.append((name, typ, False))
317
+ else:
318
+ required.append((name, typ, False))
319
+
320
+ if field.validator is not None:
321
+ fields_with_validators.append(field)
322
+
323
+ required.extend(optional)
324
+
325
+ pre_init = getattr(cls, "__attrs_pre_init__", None)
326
+ post_init = getattr(cls, "__attrs_post_init__", None)
327
+
328
+ if fields_with_validators:
329
+ post_init = _wrap_attrs_validators(fields_with_validators, post_init)
330
+
331
+ return cls, tuple(required), tuple(defaults), pre_init, post_init
332
+
333
+
334
+ def get_pydantic_info(obj):
335
+ """Extract field info from a Pydantic v2 BaseModel."""
336
+ if isinstance(obj, type):
337
+ cls = obj
338
+ else:
339
+ cls = obj.__origin__
340
+ hints = get_class_annotations(obj)
341
+ required = []
342
+ optional = []
343
+ defaults = []
344
+
345
+ for name, field_info in cls.model_fields.items():
346
+ typ = hints.get(name, Any)
347
+ if field_info.is_required():
348
+ required.append((name, typ, False))
349
+ else:
350
+ df = field_info.default_factory
351
+ if df is not None:
352
+ defaults.append(df)
353
+ optional.append((name, typ, True))
354
+ else:
355
+ defaults.append(field_info.default)
356
+ optional.append((name, typ, False))
357
+
358
+ required.extend(optional)
359
+ return cls, tuple(required), tuple(defaults)
360
+
361
+
362
+ def _wrap_attrs_validators(fields, post_init):
363
+ def inner(obj):
364
+ for field in fields:
365
+ field.validator(obj, field, getattr(obj, field.name))
366
+ if post_init is not None:
367
+ post_init(obj)
368
+
369
+ return inner
370
+
371
+
372
+ def rebuild(cls, kwargs):
373
+ """Used to unpickle Structs with keyword-only fields"""
374
+ return cls(**kwargs)
375
+
376
+
377
+ def convert_generic_alias(origin, args):
378
+ # subscribed typing._GenericAlias instances are cached within the typing module
379
+ # we make use of this fact, by storing a __structtype_cache__ attribute on the
380
+ # subscribed instance. only subscribed types are cached, so
381
+ # 'typing._GenericAlias(list, int) is typing._GenericAlias(list, int)' would be
382
+ # false.
383
+ # to achieve the same behaviour when re-creating a typing._GenericAlias from a
384
+ # types.GenericAlias, we first construct a temporary *unbound*
385
+ # typing._GenericAlias, on which we then call __getattr__. effectively doing
386
+ # typing._GenericAlias(list, T)[int], for which
387
+ # 'typing._GenericAlias(list, T)[int] is typing._GenericAlias(list, T)[int]'
388
+ # holds true
389
+ try:
390
+ params = origin.__parameters__
391
+ except AttributeError:
392
+ if not isinstance(origin, type):
393
+ # a special form such as 'typing.Literal', whose args are values rather than
394
+ # type parameters. Ordinary subscription yields the canonical, interned
395
+ # alias of the right subclass (e.g. 'typing.Literal[...]' -> '_LiteralGenericAlias').
396
+ return origin[args]
397
+
398
+ # a non-generic class with type arguments. only reachable for e.g.
399
+ # manually-built 'types.GenericAlias' instances and is probably nonsense or at
400
+ # least not somthing we can meaningfully represent, so complain about it here,
401
+ # rather than silently dropping the arguments.
402
+ raise TypeError(f"{origin.__name__!r} is not a generic type")
403
+
404
+ # a regularly-parametrised generic. Create a new typing._GenericAlias with the
405
+ # origin's unbound type params (e.g. for a 'Mapping[str, int]' this is a
406
+ # '_GenericAlias(Mapping, (~K, ~V))'), then bind it to the concrete args by
407
+ # subscripting with the args *tuple* (i.e. 'alias[(int, str)]', not
408
+ # 'alias[int, str]'), so generics with more than one type var work
409
+ alias = _GenericAlias(origin, params)
410
+ return alias[args]
structtype/_version.py ADDED
@@ -0,0 +1,24 @@
1
+ # file generated by vcs-versioning
2
+ # don't change, don't track in version control
3
+ from __future__ import annotations
4
+
5
+ __all__ = [
6
+ "__version__",
7
+ "__version_tuple__",
8
+ "version",
9
+ "version_tuple",
10
+ "__commit_id__",
11
+ "commit_id",
12
+ ]
13
+
14
+ version: str
15
+ __version__: str
16
+ __version_tuple__: tuple[int | str, ...]
17
+ version_tuple: tuple[int | str, ...]
18
+ commit_id: str | None
19
+ __commit_id__: str | None
20
+
21
+ __version__ = version = '0.3.0'
22
+ __version_tuple__ = version_tuple = (0, 3, 0)
23
+
24
+ __commit_id__ = commit_id = None
structtype/py.typed ADDED
File without changes
@@ -0,0 +1,80 @@
1
+ Metadata-Version: 2.4
2
+ Name: structtype
3
+ Version: 0.3.0
4
+ Summary: Fast Struct type with validation and JSON serialization for Python.
5
+ Maintainer-email: Wolfgang Langner <tds333@mailbox.org>
6
+ License-Expression: BSD-3-Clause
7
+ Project-URL: Homepage, https://github.com/tds333/structtype
8
+ Project-URL: Issue Tracker, https://github.com/tds333/structtype/issues
9
+ Project-URL: Source, https://github.com/tds333/structtype
10
+ Keywords: JSON,schema,serialization,struct,validation
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Environment :: WebAssembly :: Emscripten
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Programming Language :: Python :: 3.14
18
+ Classifier: Programming Language :: Python :: 3.15
19
+ Requires-Python: >=3.10
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ Dynamic: license-file
23
+
24
+ # structtype
25
+
26
+ Fast Struct type with validation + JSON serialization for Python.
27
+
28
+ - **High performance** — 5-60x faster than dataclasses, attrs, or pydantic for common operations
29
+ - **Schema validation** — familiar Python type annotations, enforced at decode time
30
+ - **No runtime dependencies** — built on a monolithic C extension based on msgspec
31
+ - **Rich type support** — nested Structs, dataclasses, TypedDicts, enums, UUID, Decimal, datetime, and more
32
+ - **JSON Schema generation** — auto-generate JSON Schema 2020-12 / OpenAPI 3.1 specs
33
+
34
+ ## Install
35
+
36
+ ```bash
37
+ pip install structtype
38
+ ```
39
+
40
+ ```bash
41
+ uv add structtype
42
+ ```
43
+
44
+ Requires Python ≥ 3.10.
45
+
46
+ ## Quick Example
47
+
48
+ ```python
49
+ from structtype import Struct
50
+
51
+ class User(Struct):
52
+ name: str
53
+ groups: set[str] = set()
54
+ email: str | None = None
55
+
56
+ alice = User("alice", groups={"admin", "engineering"})
57
+
58
+ # Serialize to JSON
59
+ alice.struct_dump_json()
60
+ # b'{"name":"alice","groups":["admin","engineering"],"email":null}'
61
+
62
+ # Deserialize and validate
63
+ User.struct_validate_json(
64
+ b'{"name":"alice","groups":["admin","engineering"],"email":null}'
65
+ )
66
+ # User(name='alice', groups={"admin", "engineering"}, email=None)
67
+ ```
68
+
69
+ ## Documentation
70
+
71
+ Full documentation is available at **https://tds333.github.io/structtype/**.
72
+
73
+ ## Benchmarks
74
+
75
+ structtype is as fast as msgspec and about 3-5x faster than pydantic.
76
+
77
+ ## License
78
+
79
+ New BSD. See the [License File](LICENSE).
80
+ The core is based on the work of Jim Crist-Harif from msgspec.
@@ -0,0 +1,14 @@
1
+ structtype/__init__.py,sha256=QcV_qQ6ybqunYrm9DTCpJjUAN3LYXD2v0RAjLd19Ops,387
2
+ structtype/__init__.pyi,sha256=P52MMOcSyIcqq0vpRUwJ-A7Xs3_l3qNbkTsALkFzJYI,11101
3
+ structtype/_adapter.py,sha256=ybqo8Bnk5qZaLeT4AjIyvGCm8gwIEaSUskaPY4c8JYY,4106
4
+ structtype/_core.cp313-win_amd64.pyd,sha256=bp_YTrcoZ1GT22znbxp7iGY8EBWexQKj_nU-PNwKrPY,283648
5
+ structtype/_inspect.py,sha256=ItEA54f13J3wOB4hdMVqFzyqLf4YL5Ozv7MyS6hWJFU,34523
6
+ structtype/_json_schema.py,sha256=-KuNxyyc8YVI2o-ZH75VRbjAyc3EuZoBd_Q_TWP7ol0,19315
7
+ structtype/_utils.py,sha256=fdXcdqu257GEHuhoB3WIIzt3yv4T7xr0lwDShMwLtaQ,14335
8
+ structtype/_version.py,sha256=mOtRuCjwCaETSVM2o_uwyhF-FRPR1OeF-XL-EF6phZM,544
9
+ structtype/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
+ structtype-0.3.0.dist-info/licenses/LICENSE,sha256=leWp9SAS7Rk9nG38qtpl86hT6BZYsog7p3xfMkTWSBg,1499
11
+ structtype-0.3.0.dist-info/METADATA,sha256=HAzB5GRmPdZx0F2M5X7vA5oEbBLpA0AyX5p9NOOfi5Q,2528
12
+ structtype-0.3.0.dist-info/WHEEL,sha256=AP_wkikJcUkDPUGvTNydv7g0R7BhW1u_fVHNKW7TJyI,101
13
+ structtype-0.3.0.dist-info/top_level.txt,sha256=hQPg6sdg7t2YGfKJRsRLTGlfSNSvjW9FwXH-yQxfnyQ,11
14
+ structtype-0.3.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (83.0.0)
3
+ Root-Is-Purelib: false
4
+ Tag: cp313-cp313-win_amd64
5
+
@@ -0,0 +1,27 @@
1
+ Copyright (c) 2026, Wolfgang Langner
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without
5
+ modification, are permitted provided that the following conditions are met:
6
+
7
+ 1. Redistributions of source code must retain the above copyright notice, this
8
+ list of conditions and the following disclaimer.
9
+
10
+ 2. Redistributions in binary form must reproduce the above copyright notice,
11
+ this list of conditions and the following disclaimer in the documentation
12
+ and/or other materials provided with the distribution.
13
+
14
+ 3. Neither the name of the copyright holder nor the names of its contributors
15
+ may be used to endorse or promote products derived from this software
16
+ without specific prior written permission.
17
+
18
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
22
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
25
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
26
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1 @@
1
+ structtype