GoogleCloudPlatform / GoogleCloudPlatform/appengine-python-standard

`StringProperty` value of `StructuredProperty` is coerced to `bytes` during `choices` validation → BadValueError

Ouverte
#104 0 commentaires 0 réactions 0 personnes assignées Voir sur GitHub
Langage dominant
Python
Étoiles
86
Forks
61
Métriques de merge des PR
Aucune PR mergée en 30 j

Description

## Expected Behavior

Given the following example ndb models and code:

```py
from google.appengine.ext import ndb

RED = "red"
GREEN = "green"
BLUE = "blue"

COLORS = [RED, GREEN, BLUE]

class Car(ndb.Model):
name = ndb.StringProperty(required=True)
color = ndb.StringProperty(choices=COLORS, required=True)

class Dealership(ndb.Model):
cars = ndb.StructuredProperty(Car, repeated=True)

d1 = Dealership(cars=[Car(name="Ferrari", color=RED)]).put().get()
print(d1)

d2 = Dealership.query(Dealership.cars == Car(color=RED)).get()
print(d2)

print(str(d1 == d2))
assert d1 == d2
```

...we should see:
```
Dealership(key=Key('Dealership', 1), cars=[Car(color='red', name='Ferrari')])
Dealership(key=Key('Dealership', 1), cars=[Car(color='red', name='Ferrari')])
True
```

...and there are no errors. This works fine in Python 2 with the Google Cloud SDK. 👍

## Actual Behavior

Using Python 3.9.16 and **appengine-python-standard**, however, we see:
```
BadValueError: Value b'red' for property b'cars.color' is not an allowed choice`
```
...which is confusing because `'red'` was provided as the value, not `b'red'`. 🤔🤔🤔

Digging around a bit, it appears to fail in [`StructuredProperty._comparison()`](https://github.com/GoogleCloudPlatform/appengine-python-standard/blob/882b8fa098220636a1c259a1dfaa1e307bcfcf72/src/google/appengine/ext/ndb/model.py#L2363-L2405) (triggered by the `Dealership.cars == Car(color=RED)` expression), specifically right here:

https://github.com/GoogleCloudPlatform/appengine-python-standard/blob/882b8fa098220636a1c259a1dfaa1e307bcfcf72/src/google/appengine/ext/ndb/model.py#L2381-L2382

...because the provided `color` value is converted to a `_BaseValue()` of `bytes` via `Property._get_base_value_unwrapped_as_list()` → `Property._get_base_value()` → `Property._opt_call_to_base_type()` → `Property._apply_to_values()` → `TextProperty._to_base_type()`:

https://github.com/GoogleCloudPlatform/appengine-python-standard/blob/882b8fa098220636a1c259a1dfaa1e307bcfcf72/src/google/appengine/ext/ndb/model.py#L1831-L1833

...before being compared to the allowed `choices` (which are `str`). 😞

A workaround is to adjust the values in `choices` to be of type `bytes`:
```py
RED = b"red"
GREEN = b"green"
BLUE = b"blue"
```

...and we then see the same result:
```
Dealership(key=Key('Dealership', 1), cars=[Car(color='red', name='Ferrari')])
Dealership(key=Key('Dealership', 1), cars=[Car(color='red', name='Ferrari')])
True
```

(This also works fine in Python 2 with the Google Cloud SDK. 😅)

## Steps to Reproduce the Problem

(see code above)

## Specifications

- Version: **appengine-python-standard** 1.1.3, Python 3.9.16
- Platform: MacOS (`Darwin Kernel Version 23.0.0: Fri Sep 15 14:42:42 PDT 2023; root:xnu-10002.1.13~1/RELEASE_X86_64 x86_64`)

---

## Additional Info

This does not appear to have anything to do with persisting data -- the persisted value of `Dealership.cars[].color` remains a `str`: `type(d1.cars[0].color) == str`.

It only happens during "comparison" (a key part of querying) when the model with a `StringProperty(choices=...)` is a **`StructuredProperty` of another model**. . We can see this by skipping entity creation and just calling `Dealership.query(Dealership.cars == Car(color=BLUE))`, or even `Dealership.cars == Car(color=BLUE)` as the most minimal case.

When using a standalone model, all works fine:
```py
ferrari = Car(name="Ferrari", color=RED).put().get()
print(ferrari)

red_car = Car.query(Car.color == RED).get()
print(red_car)

print(str(ferrari == red_car))
assert ferrari == red_car
```

...yields:
```
Car(key=Key('Car', 1), color='red', name='Ferrari')
Car(key=Key('Car', 1), color='red', name='Ferrari')
True
```

Guide de contribution

Ouvrir le guide de contribution

Évaluation

Cette issue n'a pas encore été évaluée.

Recevez les nouvelles issues par e-mail

Un résumé court des issues GitHub adaptées aux débutants.