Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    text
    copied!<p>The distinction between the custom <code>EndpointsAliasProperty</code>s and one of the data properties you defined is how they are used. They are all used to create a <code>protorpc</code> message, and that message is then converted into an <code>EndpointsModel</code> with your custom data in it. <strong>THIS</strong> is where the magic happens.</p> <p>Breaking it down into steps:</p> <h2>1. You specify your data</h2> <pre><code>from google.appengine.ext import ndb from endpoints_proto_datastore.ndb import EndpointsModel class MyModel(EndpointsModel): my_attr = ndb.StringProperty() </code></pre> <h2>2. You pick your fields for your method</h2> <pre><code>class MyApi(...): @MyModel.method(request_fields=('id', 'my_attr'), ...) def my_method(self, my_model_entity): ... </code></pre> <h2>3. A <code>protorpc</code> message class is defined from your fields</h2> <pre><code>&gt;&gt;&gt; request_message_class = MyModel.ProtoModel(fields=('id', 'my_attr')) &gt;&gt;&gt; request_message_class &lt;class '.MyModelProto_id_my_attr'&gt; &gt;&gt;&gt; for field in request_message_class.all_fields(): ... print field.name, ':', field.variant ... id : INT64 my_attr : STRING </code></pre> <p>This happens <a href="https://github.com/GoogleCloudPlatform/endpoints-proto-datastore/blob/511c8ca87d7548b90e9966495e9ebffd5eecab6e/endpoints_proto_datastore/ndb/model.py#L1262">every time</a> a request is handled by a method decorated with <code>@MyModel.method</code>.</p> <h2>4. A request comes in your application and a message is created</h2> <p>Using the <code>protorpc</code> message class, a message instance is parsed from the JSON which gets passed along to your Endpoints <a href="http://en.wikipedia.org/wiki/Service_provider_interface">SPI</a> (which is created by <code>endpoints.api_server</code>).</p> <p>When the request comes in to your <code>protorpc.remote.Service</code> it is <a href="https://code.google.com/p/google-protorpc/source/browse/python/protorpc/wsgi/service.py?r=60e8f9831a0cefb30ea2024f4ae96f71b1b606b4#147">decoded</a>:</p> <pre><code>&gt;&gt;&gt; from protorpc import remote &gt;&gt;&gt; protocols = remote.Protocols.get_default() &gt;&gt;&gt; json_protocol = protocols.lookup_by_content_type('application/json') &gt;&gt;&gt; request_message = json_protocol.decode_message( ... request_message_class, ... '{"id": 123, "my_attr": "some-string"}' ... ) &gt;&gt;&gt; request_message &lt;MyModelProto_id_my_attr id: 123 my_attr: u'some-string'&gt; </code></pre> <h2>5. The <code>protorpc</code> message is <a href="https://github.com/GoogleCloudPlatform/endpoints-proto-datastore/blob/511c8ca87d7548b90e9966495e9ebffd5eecab6e/endpoints_proto_datastore/ndb/model.py#L1316">cast into a datastore model</a></h2> <pre><code>entity = MyModel.FromMessage(request_message) </code></pre> <p><strong>THIS</strong> is the step you really care about. The <code>FromMessage</code> class method (also provided as part of <code>EndpointsModel</code>) loops through all the fields</p> <pre><code>for field in sorted(request_message_class.all_fields(), key=lambda field: field.number): </code></pre> <p>and for each field with a value set, turns the value into something to be added to the entity and separates based on whether the property is an <code>EndpointsAliasProperty</code> or not:</p> <pre><code> if isinstance(value_property, EndpointsAliasProperty): alias_args.append((local_name, to_add)) else: entity_kwargs[local_name] = to_add </code></pre> <p>After completing this loop, we have an ordered list <code>alias_args</code> of all key, value pairs and a dictionary <code>entity_kwargs</code> of the data attributes parsed from the message.</p> <p>Using these, first a simple entity <a href="https://github.com/GoogleCloudPlatform/endpoints-proto-datastore/blob/511c8ca87d7548b90e9966495e9ebffd5eecab6e/endpoints_proto_datastore/ndb/model.py#L1162">is created</a></p> <pre><code>entity = MyModel(**entity_kwargs) </code></pre> <p>and then each of the alias property values are set <strong>in order</strong>:</p> <pre><code>for name, value in alias_args: setattr(entity, name, value) </code></pre> <hr> <p>The extended behavior happens in <code>setattr(entity, name, value)</code>. Since <code>EndpointsAliasProperty</code> is a subclass of <a href="http://docs.python.org/2/library/functions.html#property"><code>property</code></a>, it is a descriptor and it has a <code>setter</code> which can perform some custom behavior <strong>beyond</strong> simply setting a value.</p> <p>For example, the <code>id</code> property is <a href="https://github.com/GoogleCloudPlatform/endpoints-proto-datastore/blob/511c8ca87d7548b90e9966495e9ebffd5eecab6e/endpoints_proto_datastore/ndb/model.py#L764">defined with</a>:</p> <pre><code>@EndpointsAliasProperty(setter=IdSet, property_type=messages.IntegerField) def id(self): </code></pre> <p>and the <a href="https://github.com/GoogleCloudPlatform/endpoints-proto-datastore/blob/511c8ca87d7548b90e9966495e9ebffd5eecab6e/endpoints_proto_datastore/ndb/model.py#L745"><code>setter</code></a> performs operations beyond simply setting data:</p> <pre><code>def IdSet(self, value): self.UpdateFromKey(ndb.Key(self.__class__, value)) </code></pre> <p>This particular method attempts to retrieve the entity stored in the datastore using the <code>id</code> and patch in any values from the datastore that were not included in the entity parsed from the request.</p> <hr> <p>If you wanted to do this for a field like <code>my_attr</code>, you would need to construct a custom query which could retrieve the item with that unique <code>my_attr</code> value (or fail if not exactly one such entity exists).</p> <p>This is problematic and you'd be better off using a unique field like the key or ID used to store the entity in the datastore.</p> <p>The <a href="http://endpoints-proto-datastore.appspot.com/examples/keys_with_ancestors.html">keys with ancestors</a> sample gives a great example of creating your own custom properties.</p> <p>If you <strong>REALLY</strong> insist on using <code>my_attr</code> to retrieve an entity, you could do so using a different property name (since <code>my_attr</code> is already used for the data property) such as <code>fromMyAttr</code>:</p> <pre><code>class MyModel(EndpointsModel): def MyAttrSet(self, value): ... @EndpointsAliasProperty(setter=MyAttrSet) def fromMyAttr(self): ... </code></pre> <p>Here, the <code>MyAttrSet</code> instance method would form the query:</p> <pre><code> def MyAttrSet(self, value): query = MyModel.query(MyModel.my_attr == value) results = query.fetch(2) </code></pre> <p>reject results that aren't unique for <code>my_attr</code>:</p> <pre><code> if len(results) == 0: raise endpoints.NotFoundException('Not found.') if len(results) == 2: raise endpoints.BadRequestException('Colliding results.') </code></pre> <p>and copy over the values for the already stored entity if we do find a unique one:</p> <pre><code> matching_entity = results[0] self._CopyFromEntity(matching_entity) self._from_datastore = True </code></pre>
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload