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