11from dataclasses import dataclass
2+ from datetime import datetime , timedelta
3+ from enum import Enum
4+ from typing import NotRequired , Required , TypedDict , get_args , get_origin
25from uuid import UUID
36
4- from google .protobuf .descriptor_pb2 import FileDescriptorSet
7+ import numpy as np
8+ from google .protobuf import duration_pb2 , timestamp_pb2
9+ from google .protobuf .descriptor_pb2 import FieldDescriptorProto , FileDescriptorSet
10+ from shapely import Geometry
511
6- from tilebox .datasets .datasets .v1 import core_pb2 , dataset_type_pb2 , datasets_pb2
12+ from tilebox .datasets .datasets .v1 import core_pb2 , dataset_type_pb2 , datasets_pb2 , well_known_types_pb2
713from tilebox .datasets .uuid import uuid_message_to_optional_uuid , uuid_message_to_uuid , uuid_to_uuid_message
814
915
16+ class DatasetKind (Enum ):
17+ TEMPORAL = dataset_type_pb2 .DATASET_KIND_TEMPORAL
18+ """A dataset that contains a timestamp field."""
19+ SPATIOTEMPORAL = dataset_type_pb2 .DATASET_KIND_SPATIOTEMPORAL
20+ """A dataset that contains a timestamp field and a geometry field."""
21+
22+
23+ _dataset_kind_int_to_enum = {kind .value : kind for kind in DatasetKind }
24+
25+
1026@dataclass (frozen = True )
1127class FieldAnnotation :
1228 description : str
@@ -20,6 +36,116 @@ def to_message(self) -> dataset_type_pb2.FieldAnnotation:
2036 return dataset_type_pb2 .FieldAnnotation (description = self .description , example_value = self .example_value )
2137
2238
39+ class FieldDict (TypedDict ):
40+ name : Required [str ]
41+ type : Required [
42+ type [str ]
43+ | type [list [str ]]
44+ | type [bytes ]
45+ | type [list [bytes ]]
46+ | type [bool ]
47+ | type [list [bool ]]
48+ | type [int ]
49+ | type [list [int ]]
50+ | type [np .uint64 ]
51+ | type [list [np .uint64 ]]
52+ | type [float ]
53+ | type [list [float ]]
54+ | type [timedelta ]
55+ | type [list [timedelta ]]
56+ | type [datetime ]
57+ | type [list [datetime ]]
58+ | type [UUID ]
59+ | type [list [UUID ]]
60+ | type [Geometry ]
61+ | type [list [Geometry ]]
62+ ]
63+ description : NotRequired [str ]
64+ example_value : NotRequired [str ]
65+
66+
67+ _TYPE_INFO : dict [type , tuple [FieldDescriptorProto .Type .ValueType , str | None ]] = {
68+ str : (FieldDescriptorProto .TYPE_STRING , None ),
69+ bytes : (FieldDescriptorProto .TYPE_BYTES , None ),
70+ bool : (FieldDescriptorProto .TYPE_BOOL , None ),
71+ int : (FieldDescriptorProto .TYPE_INT64 , None ),
72+ np .uint64 : (FieldDescriptorProto .TYPE_UINT64 , None ),
73+ float : (FieldDescriptorProto .TYPE_DOUBLE , None ),
74+ timedelta : (FieldDescriptorProto .TYPE_MESSAGE , f".{ duration_pb2 .Duration .DESCRIPTOR .full_name } " ),
75+ datetime : (FieldDescriptorProto .TYPE_MESSAGE , f".{ timestamp_pb2 .Timestamp .DESCRIPTOR .full_name } " ),
76+ UUID : (FieldDescriptorProto .TYPE_MESSAGE , f".{ well_known_types_pb2 .UUID .DESCRIPTOR .full_name } " ),
77+ Geometry : (FieldDescriptorProto .TYPE_MESSAGE , f".{ well_known_types_pb2 .Geometry .DESCRIPTOR .full_name } " ),
78+ }
79+
80+
81+ @dataclass (frozen = True )
82+ class Field :
83+ descriptor : FieldDescriptorProto
84+ annotation : FieldAnnotation
85+ queryable : bool
86+
87+ @classmethod
88+ def from_message (cls , field : dataset_type_pb2 .Field ) -> "Field" :
89+ return cls (
90+ descriptor = field .descriptor ,
91+ annotation = FieldAnnotation .from_message (field .annotation ),
92+ queryable = field .queryable ,
93+ )
94+
95+ @classmethod
96+ def from_dict (cls , field : FieldDict ) -> "Field" :
97+ origin = get_origin (field ["type" ])
98+ if origin is list :
99+ label = FieldDescriptorProto .Label .LABEL_REPEATED
100+ args = get_args (field ["type" ])
101+ inner_type = args [0 ] if args else field ["type" ]
102+ else :
103+ label = FieldDescriptorProto .Label .LABEL_OPTIONAL
104+ inner_type = field ["type" ]
105+
106+ (field_type , field_type_name ) = _TYPE_INFO [inner_type ]
107+
108+ return cls (
109+ descriptor = FieldDescriptorProto (
110+ name = field ["name" ],
111+ type = field_type ,
112+ type_name = field_type_name ,
113+ label = label ,
114+ ),
115+ annotation = FieldAnnotation (
116+ description = field .get ("description" , "" ),
117+ example_value = field .get ("example_value" , "" ),
118+ ),
119+ queryable = False ,
120+ )
121+
122+ def to_message (self ) -> dataset_type_pb2 .Field :
123+ return dataset_type_pb2 .Field (
124+ descriptor = self .descriptor ,
125+ annotation = self .annotation .to_message (),
126+ queryable = self .queryable ,
127+ )
128+
129+
130+ @dataclass (frozen = True )
131+ class DatasetType :
132+ kind : DatasetKind | None
133+ fields : list [Field ]
134+
135+ @classmethod
136+ def from_message (cls , dataset_type : dataset_type_pb2 .DatasetType ) -> "DatasetType" :
137+ return cls (
138+ kind = _dataset_kind_int_to_enum .get (dataset_type .kind , None ),
139+ fields = [Field .from_message (f ) for f in dataset_type .fields ],
140+ )
141+
142+ def to_message (self ) -> dataset_type_pb2 .DatasetType :
143+ return dataset_type_pb2 .DatasetType (
144+ kind = self .kind .value if self .kind else dataset_type_pb2 .DATASET_KIND_UNSPECIFIED ,
145+ fields = [f .to_message () for f in self .fields ],
146+ )
147+
148+
23149@dataclass (frozen = True )
24150class AnnotatedType :
25151 descriptor_set : FileDescriptorSet
0 commit comments