Coverage for gws-app/gws/base/model/util.py: 54%
37 statements
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-05 13:35 +0200
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-05 13:35 +0200
1"""Model utilities."""
3from typing import Iterable
4import datetime
6import gws
7import gws.base.feature
9_ATTR_TO_PY = {
10 gws.AttributeType.bool: bool,
11 gws.AttributeType.bytes: bytes,
12 gws.AttributeType.date: datetime.date,
13 gws.AttributeType.datetime: datetime.datetime,
14 # gws.AttributeType.feature
15 # gws.AttributeType.featurelist
16 gws.AttributeType.float: float,
17 # gws.AttributeType.floatlist
18 # gws.AttributeType.geometry
19 gws.AttributeType.int: int,
20 # gws.AttributeType.intlist
21 gws.AttributeType.str: str,
22 # gws.AttributeType.strlist
23 gws.AttributeType.time: datetime.time,
24}
27def iter_features(features: list[gws.Feature], mc: gws.ModelContext) -> Iterable[gws.Feature]:
28 """Iterate over features and their related features.
30 Related features are features stored in attributes, directly or in lists. They are
31 visited depth-first, as long as the relation depth of the context is below its maximum.
33 Args:
34 features: The features.
35 mc: The model context.
37 Yields:
38 Each feature, followed by its related features.
39 """
40 for f in features:
41 yield f
43 if mc.relDepth >= mc.maxDepth:
44 continue
46 sub = []
48 for val in f.attributes.values():
49 if isinstance(val, gws.base.feature.Feature):
50 sub.append(val)
51 elif isinstance(val, list):
52 for v in val:
53 if isinstance(v, gws.base.feature.Feature):
54 sub.append(v)
56 yield from iter_features(sub, secondary_context(mc))
59def describe_from_record(fd: gws.FeatureRecord) -> gws.DataSetDescription:
60 """Create a dataset description from a feature record.
62 Column types are derived from the python types of the attribute values. If the record
63 has a shape, a ``geometry`` column is added.
65 Args:
66 fd: The feature record.
68 Returns:
69 The dataset description.
70 """
71 py_to_attr = {str(v): k for k, v in _ATTR_TO_PY.items()}
73 desc = gws.DataSetDescription(columns=[])
75 for k, v in fd.attributes:
76 typ = str(type(v))
77 desc.columns.append(gws.ColumnDescription(
78 name=k,
79 nativeType=typ,
80 type=py_to_attr.get(typ, gws.AttributeType.str),
81 ))
82 if fd.shape:
83 col = gws.ColumnDescription(
84 name='geometry',
85 geometryType=fd.shape.type,
86 geometrySrid=fd.shape.crs.srid,
87 type=gws.AttributeType.geometry,
88 )
89 desc.columns.append(col)
90 desc.geometryName = col.name
91 desc.geometryType = col.geometryType
92 desc.geometrySrid = col.geometrySrid
94 desc.columnMap = {col.name: col for col in desc.columns}
95 return desc
98def copy_context(mc: gws.ModelContext, **kwargs) -> gws.ModelContext:
99 """Copy a model context.
101 Args:
102 mc: The model context.
103 **kwargs: Properties to set in the copy.
105 Returns:
106 A new model context.
107 """
108 return gws.ModelContext(gws.u.merge(mc, kwargs))
110def secondary_context(mc: gws.ModelContext, **kwargs) -> gws.ModelContext:
111 """Copy a model context for related features, with the relation depth increased by one.
113 Args:
114 mc: The model context.
115 **kwargs: Properties to set in the copy.
117 Returns:
118 A new model context.
119 """
120 return gws.ModelContext(gws.u.merge(mc, kwargs, relDepth=mc.relDepth + 1))