Search code examples
pythongoogle-app-enginegoogle-cloud-datastore

Google Datastore: iterator already started - How to work with these iterators?


I get the error "Iterator already started" when using the results of a Datastore query with AppEngine/Flask/Python and would appreciate some help.

I have a number of users in the Google Datastore, and each user has a 'role' which is a number in the range 0..10. When listing the users I want to print "Administrator" for users with role 0, and "User" for all the other users.

The relevant snippet from the html template file is here:

 {% for each_user in users %}
   <tr>
    <td> <a href="user_display?find={{each_user.user_id}}" >{{ each_user.user_id}}</a></td>     
    <td> {{ each_user.first_name}} </td>
    <td> {{ each_user.last_name}} </td>
    <td> {{ each_user.role}} </td>   
  </tr>       
  {% endfor %} 

My first, unlucky attempt is below. It crashes with the error "Iterator already started". (Note - it might be a bit inaccurate as I lost the original after many changes, but you get the idea).

q_users = datastore_client.query(kind='user')
q_users.order = ['first_name']
users = q_users.fetch()

for item in users:
    if item['role'] == 0:
        item['role'] = "Administrator"
    else:
        item['role'] = "User"

}
return render_template('users_list.html', users=users) 

Investigating in different places it turns out that the result of a Datastore query is not a list of dictionaries but an iterator object like:

<google.cloud.datastore.query.Iterator object at 0x.......>

So, as an interim solution, I wrote a loop that walks through the iterator and copies the entities one by one to a list of dictionaries changing the role on the fly. It works, but the code is ugly and is very impractical:

q_users = datastore_client.query(kind='user')
q_users.order = ['first_name']
users = q_users.fetch()

new_u = []
# Loop through iterator returned by Datastore
for u in users:  
    # Assign a string corresponding to the user's role
    if u['role'] == 0: 
        u_role = 'Admin'
    else:
        u_role = 'User'

    # Now append to the new list of users
    new_u.append({'user_id': u['user_id'],
                 'first_name': u['first_name'],
                 'last_name': u['last_name'],
                 'role': u_role})

# Send to screen
return render_template('users_list.html', users=new_u) 

Surely there is a better way. Any suggestions?

Thanks!


Solution

  • Try:

    users = list(q_users.fetch())
    

    Also, test for each_user['role'] in the template.