blob: 904e3fcbd5cb716735c4d747c89cb2384ff00596 [file] [log] [blame]
// Copyright 2019 The LUCI Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package starlarkproto exposes protobuf messages as Starlark types.
//
// Internally a message is stored as a tree of Starlark values, with some type
// checking done when manipulating fields, based on proto message descriptors
// loaded dynamically from a serialized FileDescriptorSet.
//
// For example, reading or assigning to a field not defined in a message will
// cause a runtime error. Similarly, trying to assign a value of a wrong type to
// a non-repeated field will fail.
//
// Instantiating messages and default field values
//
// Each proto message in a loaded package is exposed via a constructor function
// that takes optional keyword arguments and produces a new object of *Message
// type.
//
// All unassigned fields are implicitly set to their default zero values on
// first access, including message-typed fields, lists and maps. It means, for
// example, if a message 'a' has a singular field 'b', that has a field 'c', it
// is always fine to write 'a.b.c' to read or set 'c' value, without explicitly
// checking that 'b' is set.
//
// To clear a field, assign None to it (regardless of its type).
//
// References and aliasing
//
// Messages are passed around everywhere by reference. In particular it is
// possible to have multiple fields pointing to the exact same message, e.g.
//
// m1 = M()
// m2 = M()
// a = A()
// m1.f = a
// m2.f = a
// a.i = 123 # changes both m1.f.i and m2.f.i
//
// Note that 'm1.f = a' assignment checks the type of 'a', it should either
// match type of 'f' identically (no duck typing), or be a dict or None (which
// will be converted to messages, see below).
//
// Working with repeated fields and maps
//
// Values of repeated fields are represented by special sequence types that
// behave as strongly-typed lists. Think of them as list[T] types or as
// hypothetical 'message List<T>' messages.
//
// When assigning a non-None value R to a repeated field F of type list[T],
// the following rules apply (sequentially):
// 1. If R is not a sequence => error.
// 2. If R has type list[T'], then
// a. If T == T', then F becomes an alias of R.
// b. If T != T' => error.
// 3. A new list[T] is instantiated from R and assigned to F.
//
// Notice that rule 2 is exactly like the aliasing rule for messages. This is
// where "think of them as 'message List<T>'" point of view comes into play.
//
// As a concrete example, consider this:
//
// m1 = M()
// m1.int64s = [1, 2] # a list is implicitly converted (copied) to list[T]
//
// m2 = M()
// m2.int64s = m1.int64s # points to the exact same list[T] object now
// m2.int64s.append(3) # updates both m1 and m2
//
// m1.int32s = m1.int64s # error! list[int64] is not list[int32]
// m1.int32s = list(m1.int64s) # works now (by making a copy of a list copy)
//
// Maps behave in the similar way. Think of them as strongly-typed map<K,V>
// values or as 'message List<Pair<K,V>>' messages. They can be implicitly
// instantiated from iterable mappings (e.g. dicts).
//
// Auto-conversion of dicts and None's into Messages
//
// When assigning to a message-typed value (be it a field, an element of a
// list[T] or a value of map<K,V>) dicts are implicitly converted into messages
// as if via 'T(**d)' call. Similarly, None's are converted into empty messages,
// as if via 'T()' call.
//
// Differences from starlarkproto (beside using different guts):
// * Message types are instantiated through proto.new_loader().
// * Text marshaller appends\removes trailing '\n' somewhat differently.
// * Text marshaller marshals empty messages as '<>' (without line breaks).
// * Bytes fields are represented as str, not as []uint8{...}.
// * proto.to_jsonpb doesn't have 'emit_defaults' kwarg (it is always False).
// * Better support for proto2 messages.
package starlarkproto
// TODO: support more known types (any, struct).
// TODO: delete struct_to_textpb, use dynamic protos instead