Загрузка объектов хранилища данных из проекта Python в Go приводит к вложенным structs slices of slices error

Я пишу модуль в своем проекте Google AppEngine в Go по соображениям производительности, но должен иметь возможность читать некоторые из объектов, которые у меня есть в хранилище данных. Я написал код Go, чтобы читать объекты, созданные в Python, но я получаю следующую ошибку:

datastore: flattening nested structs leads to a slice of slices: field "Messages"

Определения модели в Python:

 class ModelB(ndb.Model): msg_id = ndb.StringProperty(indexed=False) cat_ids = ndb.StringProperty(repeated=True, indexed=False) list_ids = ndb.StringProperty(repeated=True, indexed=False) default_list_id_index = ndb.IntegerProperty(indexed=False) class ModelA(ndb.Model): date_join = ndb.DateTimeProperty(auto_now_add=True) name = ndb.StringProperty() owner_salutation = ndb.StringProperty(indexed=False) owner_email_address = ndb.StringProperty() logo_url = ndb.StringProperty(indexed=False) ... messages = ndb.LocalStructuredProperty(ModelB, name='bm', repeated=True) 

И в Go:

 type ModelB struct { MessageID string `datastore:"msg_id,noindex"` CategoryIDs []string `datastore:"cat_ids,noindex"` ListIDs []string `datastore:"list_ids,noindex"` DefaultListIDIndex int `datastore:"default_list_id_index,noindex"` } type ModelA struct { DateJoin time.Time `datastore:"date_join,"` Name string `datastore:"name,"` OwnerSalutation string `datastore:"owner_salutation,noindex"` OwnerEmailAddress string `datastore:"owner_email_address,"` LogoURL string `datastore:"logo_url,noindex"` Messages []ModelB `datastore:"bm,"` } 

Что-то я здесь делаю неправильно? Является ли только несовместимость функций между определениями модели Go vs Python?

Попытка декодирования ModelB

ModelA следующим образом:

 import pb "appengine_internal/datastore" import proto "code.google.com/p/goprotobuf/proto" type ModelA struct { DateJoin time.Time `datastore:"date_join,"` Name string `datastore:"name,"` OwnerSalutation string `datastore:"owner_salutation,noindex"` OwnerEmailAddress string `datastore:"owner_email_address,"` LogoURL string `datastore:"logo_url,noindex"` Messages []ModelB `datastore:"-"` } // Load is implemented for the PropertyLoaderSaver interface. func (seller *ModelA) Load(c <-chan datastore.Property) error { f := make(chan datastore.Property, 100) for p := range c { if p.Name == "bm" { var val pb.EntityProto err := proto.Unmarshal([]byte(p.Value.(string)), &val) if err != nil { return err } //TODO: Store result as a new ModelB } else { f <- p } } close(f) return datastore.LoadStruct(seller, f) } 

Но я получаю следующую ошибку: proto: required field "{Unknown}" not set

Пакет хранилища данных Go не поддерживает два слоя таких фрагментов. Вы можете иметь []ModelB , если ModelB не содержит никаких фрагментов. Или вы можете использовать ModelB в ModelA , а ModelB может иметь в нем срезы. Но вы не можете иметь оба []ModelB а ModelB – срезы. См. Код для условия ошибки. Ваши варианты:

  1. не делай этого в Go
  2. напишите свой десериализатор хранилища данных, чтобы обработать этот случай – это, вероятно, сложно
  3. измените свои структуры данных python, чтобы удовлетворить требованиям Go и переписать свои данные

Я думаю, если вы копаете достаточно, вы найдете ответ:

Во-первых, при определении свойств LocalStructuredProperty в Python вам необходимо установить keep_keys=True

 class ModelB(ndb.Model): msg_id = ndb.StringProperty(indexed=False) cat_ids = ndb.StringProperty(repeated=True, indexed=False) list_ids = ndb.StringProperty(repeated=True, indexed=False) default_list_id_index = ndb.IntegerProperty(indexed=False) class ModelA(ndb.Model): date_join = ndb.DateTimeProperty(auto_now_add=True) name = ndb.StringProperty() owner_salutation = ndb.StringProperty(indexed=False) owner_email_address = ndb.StringProperty() logo_url = ndb.StringProperty(indexed=False) ... messages = ndb.LocalStructuredProperty(ModelB, name='bm', repeated=True, keep_keys=True) 

Простое переопределение в моем коде и отображение над моими сущностями, выполняющими put() для каждого исправленного представления.

Тогда в моем коде Go:

 type ModelB struct { MessageID string `datastore:"msg_id,noindex"` CategoryIDs []string `datastore:"cat_ids,noindex"` ListIDs []string `datastore:"list_ids,noindex"` DefaultListIDIndex int `datastore:"default_list_id_index,noindex"` } type ModelA struct { DateJoin time.Time `datastore:"date_join,"` Name string `datastore:"name,"` OwnerSalutation string `datastore:"owner_salutation,noindex"` OwnerEmailAddress string `datastore:"owner_email_address,"` LogoURL string `datastore:"logo_url,noindex"` Messages []ModelB `datastore:"-"` } // Load is implemented for the PropertyLoaderSaver interface. func (s *ModelA) Load(c <-chan datastore.Property) (err error) { f := make(chan datastore.Property, 32) errc := make(chan error, 1) defer func() { if err == nil { err = <-errc } }() go func() { defer close(f) for p := range c { if p.Name == "bm" { var b ModelB err := loadLocalStructuredProperty(&b, []byte(p.Value.(string))) if err != nil { errc <- err return } s.Messages = append(s.Messages, b) } else { f <- p } } errc <- nil }() return datastore.LoadStruct(s, f) } 

Мне пришлось скопировать кучу из пакета appengine/datastore , поскольку ключевая функция не была экспортирована и чтобы упростить объем кода, который мне нужен для копирования, я отказался от поддержки типов Reference . Я открыл билет на трекер проблемы, чтобы узнать, можно loadEntity экспортировать экспортированную функцию loadEntity : https://code.google.com/p/googleappengine/issues/detail?id=10426

 import ( "errors" "time" "appengine" "appengine/datastore" pb "appengine_internal/datastore" proto "code.google.com/p/goprotobuf/proto" ) func loadLocalStructuredProperty(dst interface{}, raw_proto []byte) error { var val pb.EntityProto err := proto.Unmarshal(raw_proto, &val) if err != nil { return err } return loadEntity(dst, &val) } //Copied from appengine/datastore since its not exported // loadEntity loads an EntityProto into PropertyLoadSaver or struct pointer. func loadEntity(dst interface{}, src *pb.EntityProto) (err error) { c := make(chan datastore.Property, 32) errc := make(chan error, 1) defer func() { if err == nil { err = <-errc } }() go protoToProperties(c, errc, src) if e, ok := dst.(datastore.PropertyLoadSaver); ok { return e.Load(c) } return datastore.LoadStruct(dst, c) } func protoToProperties(dst chan<- datastore.Property, errc chan<- error, src *pb.EntityProto) { defer close(dst) props, rawProps := src.Property, src.RawProperty for { var ( x *pb.Property noIndex bool ) if len(props) > 0 { x, props = props[0], props[1:] } else if len(rawProps) > 0 { x, rawProps = rawProps[0], rawProps[1:] noIndex = true } else { break } var value interface{} if x.Meaning != nil && *x.Meaning == pb.Property_INDEX_VALUE { value = indexValue{x.Value} } else { var err error value, err = propValue(x.Value, x.GetMeaning()) if err != nil { errc <- err return } } dst <- datastore.Property{ Name: x.GetName(), Value: value, NoIndex: noIndex, Multiple: x.GetMultiple(), } } errc <- nil } func fromUnixMicro(t int64) time.Time { return time.Unix(t/1e6, (t%1e6)*1e3) } // propValue returns a Go value that combines the raw PropertyValue with a // meaning. For example, an Int64Value with GD_WHEN becomes a time.Time. func propValue(v *pb.PropertyValue, m pb.Property_Meaning) (interface{}, error) { switch { case v.Int64Value != nil: if m == pb.Property_GD_WHEN { return fromUnixMicro(*v.Int64Value), nil } else { return *v.Int64Value, nil } case v.BooleanValue != nil: return *v.BooleanValue, nil case v.StringValue != nil: if m == pb.Property_BLOB { return []byte(*v.StringValue), nil } else if m == pb.Property_BLOBKEY { return appengine.BlobKey(*v.StringValue), nil } else { return *v.StringValue, nil } case v.DoubleValue != nil: return *v.DoubleValue, nil case v.Referencevalue != nil: return nil, errors.New("Not Implemented!") } return nil, nil } // indexValue is a Property value that is created when entities are loaded from // an index, such as from a projection query. // // Such Property values do not contain all of the metadata required to be // faithfully represented as a Go value, and are instead represented as an // opaque indexValue. Load the properties into a concrete struct type (eg by // passing a struct pointer to Iterator.Next) to reconstruct actual Go values // of type int, string, time.Time, etc. type indexValue struct { value *pb.PropertyValue } 

Решение кем-то1 отлично работает, но у меня много миллионов сущностей, и мне не нужно было переставлять их все (чтобы добавить keep_keys = True в LocalStructuredProperty).

Итак, я создал сокращенную версию EntityProto которая удаляет зависимость от ключа и пути и т. Д. Просто замените pb.EntityProto на LocalEntityProto и существующие объекты, написанные на LocalEntityProto должны загрузить ОК (я использую PropertyLoadSaver для вложенная структура).

Отказ от ответственности: я использую это только для чтения с Go – я не пробовал записывать те же объекты назад, чтобы проверить, все ли они загружаются в Python.

 import pb "google.golang.org/appengine/internal/datastore" import proto "github.com/golang/protobuf/proto" type LocalEntityProto struct { Kind *pb.EntityProto_Kind `protobuf:"varint,4,opt,name=kind,enum=appengine.EntityProto_Kind" json:"kind,omitempty"` KindUri *string `protobuf:"bytes,5,opt,name=kind_uri" json:"kind_uri,omitempty"` Property []*pb.Property `protobuf:"bytes,14,rep,name=property" json:"property,omitempty"` RawProperty []*pb.Property `protobuf:"bytes,15,rep,name=raw_property" json:"raw_property,omitempty"` Rank *int32 `protobuf:"varint,18,opt,name=rank" json:"rank,omitempty"` XXX_unrecognized []byte `json:"-"` } func (m *LocalEntityProto) Reset() { *m = LocalEntityProto{} } func (m *LocalEntityProto) String() string { return proto.CompactTextString(m) } func (*LocalEntityProto) ProtoMessage() {} func (m *LocalEntityProto) GetKind() pb.EntityProto_Kind { if m != nil && m.Kind != nil { return *m.Kind } return pb.EntityProto_GD_CONTACT } func (m *LocalEntityProto) GetKindUri() string { if m != nil && m.KindUri != nil { return *m.KindUri } return "" } func (m *LocalEntityProto) GetProperty() []*pb.Property { if m != nil { return m.Property } return nil } func (m *LocalEntityProto) GetRawProperty() []*pb.Property { if m != nil { return m.RawProperty } return nil } func (m *LocalEntityProto) GetRank() int32 { if m != nil && m.Rank != nil { return *m.Rank } return 0 }