Repository: googleapis/google-cloud-python

Issue:
datastore: No way to exclude_from_indexes new properties on updated entities
When loading an `Entity` from the datastore and updating it, there is no way to exclude new attributes from being indexed, since the only public API for setting `exclude_from_indexes` is on the `Entity` constructor. To work around this, I am currently setting `entity._exclude_from_indexes = (...)` in my code, which is not a "public" API.

Example:

```python
client = google.cloud.datastore.Client()
key = client.key('SomeEntityKey')
entity = google.cloud.datastore.Entity(key, exclude_from_indexes=('foo', 'bar'))
entity['foo'] = 'foo'
print 'entity.exclude_from_indexes:', entity.exclude_from_indexes
client.put(entity)

entity2 = client.get(entity.key)
print 'entity2.exclude_from_indexes:', entity2.exclude_from_indexes
entity2['bar'] = 'bar'
client.put(entity2)
```

Output:

```
entity.exclude_from_indexes: frozenset(['foo', 'bar'])
entity2.exclude_from_indexes: frozenset([u'foo'])
```

This is actually "expected" based on how the Datastore works, however, in the code sample above, there should be some way for me to put the entity back, without copying it into a brand new `Entity`.


Tested using:

* Mac OS X 10.11.6
* Python 2.7.10
* The following Google packages:
```
gapic-google-cloud-datastore-v1==0.15.3
google-auth==1.0.1
google-auth-httplib2==0.0.2
google-cloud-core==0.24.1
google-cloud-datastore==1.0.0
google-cloud-storage==1.1.1
google-gax==0.15.13
google-resumable-media==0.0.2
googleapis-common-protos==1.5.2
proto-google-cloud-datastore-v1==0.90.4
```



Discussion:
@evanj Sorry, I do not completely follow. Would you be willing to give me some example code with the actual expected behavior?
I would like a public API to read an entity from the datastore, then add *new* attributes that are "excluded from indexes." There doesn't seem to be a way to do it. I'd like some way to do the following using a publicly documented api:

```python
existing_entity = client.get(entity.key)
existing_entity['some_new_attribute'] = 'bar'
existing_entity.add_exclude_from_indexes('some_new_attribute')
client.put(existing_entity)
```

The way the API currently works, the attribute `'some_new_attribute'` *will* be indexed, unless I create a *new* entity using the `Entity` constructor.
@evanj Thank you. I understand now.
Hi @evanj,
I tested this today, and I can not seem to reproduce (note that I am on 1.2.0):

```python
>>> from google.cloud import datastore
>>> client = datastore.Client()
>>> key = client.key('Parent', 'foo')
>>> entity = datastore.Entity(key, exclude_from_indexes=('foo', 'bar'))
>>> entity['foo'] = 'foo'
>>> client.put(entity)
```

And then I pulled it back down:

```python
>>> entity2 = client.get(key)
>>> entity2.exclude_from_indexes
frozenset({'foo'})
```

Additionally, I stepped through [the code where it is assigned](https://github.com/GoogleCloudPlatform/google-cloud-python/blob/master/datastore/google/cloud/datastore/helpers.py#L146-L148) in PDB, and verified it.

For now, I am going to close this as working as intended, but if you can give me a reproduction case (on 1.2.0 if possible), I would be happy to investigate further.
The issue occurs when creating a new property in a previously created entity. For example:

```
>>> entity3 = client.get(key)
>>> entity3['baz'] = 'baz'
>>> # There is no way to make "baz" unindexed
>>> entity3.put()
```
Ohhh, I mis-understood. Okay, I can put that in.
I have found this issue in both ways (add and remove from exclude_from_indexes). I use this library in scripts to make background changes to all entities; e.g. to reindex a field originally tagged with noindex, to rename fields, etc.
I am going to sleep on it, but rather than add a bunch of add and remove methods, I am inclined to just change `exclude_from_indexes` to a `set` rather than a `frozenset`.
That would definitely solve the problem for me. I'm currently just assigning to `_exclude_from_indexes` which works, but I don't like using private APIs in third-party code. Thanks!

Resolving patch:
diff --git a/datastore/google/cloud/datastore/entity.py b/datastore/google/cloud/datastore/entity.py
--- a/datastore/google/cloud/datastore/entity.py
+++ b/datastore/google/cloud/datastore/entity.py
@@ -129,8 +129,9 @@ class Entity(dict):
     def __init__(self, key=None, exclude_from_indexes=()):
         super(Entity, self).__init__()
         self.key = key
-        self._exclude_from_indexes = set(_ensure_tuple_or_list(
+        self.exclude_from_indexes = set(_ensure_tuple_or_list(
             'exclude_from_indexes', exclude_from_indexes))
+        """Names of fields which are *not* to be indexed for this entity."""
         # NOTE: This will be populated when parsing a protobuf in
         #       google.cloud.datastore.helpers.entity_from_protobuf.
         self._meanings = {}
@@ -148,7 +149,7 @@ def __eq__(self, other):
             return False
 
         return (self.key == other.key and
-                self._exclude_from_indexes == other._exclude_from_indexes and
+                self.exclude_from_indexes == other.exclude_from_indexes and
                 self._meanings == other._meanings and
                 super(Entity, self).__eq__(other))
 
@@ -176,15 +177,6 @@ def kind(self):
         if self.key:
             return self.key.kind
 
-    @property
-    def exclude_from_indexes(self):
-        """Names of fields which are *not* to be indexed for this entity.
-
-        :rtype: sequence of field names
-        :returns: The set of fields excluded from indexes.
-        """
-        return frozenset(self._exclude_from_indexes)
-
     def __repr__(self):
         if self.key:
             return '<Entity%s %s>' % (self.key._flat_path,


Test patch: