dongweihuan8832 2013-12-20 19:38
浏览 83
已采纳

在Go中从Python项目中加载数据存储区实体会导致嵌套的结构切片切片错误

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

  • 写回答

3条回答 默认 最新

  • duangan1945 2013-12-25 02:04
    关注

    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
    }
    
    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论
查看更多回答(2条)

报告相同问题?

悬赏问题

  • ¥15 YoloV5 第三方库的版本对照问题
  • ¥15 请完成下列相关问题!
  • ¥15 drone 推送镜像时候 purge: true 推送完毕后没有删除对应的镜像,手动拷贝到服务器执行结果正确在样才能让指令自动执行成功删除对应镜像,如何解决?
  • ¥15 求daily translation(DT)偏差订正方法的代码
  • ¥15 js调用html页面需要隐藏某个按钮
  • ¥15 ads仿真结果在圆图上是怎么读数的
  • ¥20 Cotex M3的调试和程序执行方式是什么样的?
  • ¥20 java项目连接sqlserver时报ssl相关错误
  • ¥15 一道python难题3
  • ¥15 牛顿斯科特系数表表示