英文:
Loading datastore entities from Python project in Go leads to nested 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与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
英文:
I am writing a module in my Google AppEngine project in Go for performance reasons but need to be able to read from some of the entities I have in datastore. I wrote out the Go code to be able to read the entities I built out in Python but I am getting the following error:
datastore: flattening nested structs leads to a slice of slices: field "Messages"
Model Definitions in 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)
And in 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,"`
}
Is there something I'm doing wrong here? Is just a feature incompatibility between Go vs Python model definitions?
Attempt to Decode ModelB
Re-define ModelA
as follows:
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)
}
But I receive the following error:
proto: required field "{Unknown}" not set
答案1
得分: 5
Go的数据存储包不支持像那样的两层切片。你可以有[]ModelB
,只要ModelB
不包含任何切片。或者,你可以在ModelA
中使用ModelB
,而ModelB
可以包含切片。但是你不能同时拥有[]ModelB
和ModelB
有切片。查看代码以获取错误条件。你的选择:
- 不要在Go中这样做
- 编写自己的数据存储反序列化器来处理这种情况-这可能很困难
- 更改你的Python数据结构以满足Go的要求,并重新编写你的数据
英文:
The Go datastore package doesn't support two layers of slices like that. You can have []ModelB
, as long as ModelB
doesn't contain any slices. Or, you can use ModelB
in ModelA
, and ModelB
can have slices in it. But you can't have both []ModelB
and ModelB
has slices. See the code for the error condition. Your options:
- don't do it in Go
- write your own datastore deserializer to handle this case - this is probably hard
- change your python data structures to satisfy the Go requirements and rewrite your data
答案2
得分: 3
我猜如果你足够深入地挖掘,你会找到答案:
首先,在Python中定义LocalStructuredProperty属性时,你需要设置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
函数: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 (e.g. 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
}
希望这能帮到你!
英文:
I guess if you dig enough you'll find the answer:
First off, when defining the LocalStructuredProperty properties in Python, you need to set 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)
A simple redefinition in my code and mapping over my entities doing a put()
on each fixed up the representation.
Then in my Go Code:
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)
}
I had to copy a bunch from the appengine/datastore
package as a key function wasn't exported and to simplify the amount of code I needed to copy, I dropped support for Reference
types. I opened a ticket on the issue tracker to see if we can get the loadEntity
function exported: 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 (e.g. 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
}
答案3
得分: 1
someone1的解决方案很好,但是我有很多百万个实体,并且不想重新放置它们(以添加keep_keys=True到LocalStructuredProperty)。
因此,我创建了一个简化版的EntityProto
,它去除了对键和路径等的依赖...只需将pb.EntityProto
替换为LocalEntityProto
,现有的使用Python编写的实体应该可以正常加载(我正在使用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
}
英文:
The solution by someone1 works great but I have many millions of entities and didn't want to have to re-put them all (to add the keep_keys=True to the LocalStructuredProperty).
So, I created a cut-down version of EntityProto
which removes the dependency on the key & path etc... Simply replace pb.EntityProto
with LocalEntityProto
and the existing python-written entities should load OK (I'm using a PropertyLoadSaver for the nested struct).
Disclaimer: I'm only using this to read from Go - I haven't tried writing the same entities back to see if they still load in 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
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论