-1

I have a few doubts regarding using python ndb. I am using proto datastore. I have a class User

class User( EndpointsModel ):
    name = ndb.StructuredProperty( Name, required=True )
    dateOfBirth = ndb.DateProperty(required=True)
    userName = ndb.StringProperty( required=True )
    emailId = ndb.StringProperty( required=True )

I want to fetch user entity based on usernames. When i create a new User object and do

user.id = username

I get error "ID must be an integer.", how do i overcome this. Also would user.get_by_id() be faster than User.query( User.username == username ) ?

If i want all the username's to be unique, do i have to create an entity of username's and check whenever a new user is created if its already present or is there some other neat and efficient way to do this.

Dan McGrath
  • 41,220
  • 11
  • 99
  • 130
Gaurav Saini
  • 77
  • 1
  • 7

2 Answers2

4

It sounds like you want the unique key of the entity to include and be reproduceable from the username, a common and useful thing to do.

To create a User entity with a specific ID, the ID must be set when you call the constructor:

username = 'foo'
user = User(id=username)
user.put()

This constructs a new entity object whose key is User: foo (where the key has one path part with a kind name of User and an ID of the string foo). The put() call saves it to the datastore.

To get the entity for a given username, construct the Key then try to get() it:

key = ndb.Key('User', username)
user = key.get()
if not user:
    # ....

It is common to put these together to create the User entity for a given username if it doesn't exist:

key = ndb.Key('User', username)
user = key.get()
if not user:
    user = User(id=username)
    user.put()

The recommended practice is to do this inside a datastore transaction, so two clients trying to create the first User entity for the same username don't clobber each other without knowing it.

Putting the username in the key guarantees uniqueness. And yes, it is faster to get by key than to query by a property. A query involves multiple steps to get results as a list of keys then fetch the result entities, even if there is only one result. You might want to store the username as an indexed property anyway in case you need it for other kinds of queries, but fetching the entity or just doing an existence check works with keys alone.

Shedokan
  • 1,212
  • 1
  • 10
  • 21
Dan Sanderson
  • 2,111
  • 12
  • 12
  • 1
    One of the problems with setting `username` as a key is that you can't change the username after creation... something that most of the times is needed even if it's done per request.. – Lipis Sep 09 '14 at 07:44
  • 1
    Fair point. If that's a requirement, then the uniqueness requirement gets a tad more challenging. You might need a large entity group containing one username per entity so you can reserve usernames transactionally. (Putting all of the `User` entities in a single entity group is likely to be undesirable.) Cross-group transactions would be useful for user creation and renames. With that layout, a query to locate user IDs from usernames might be the best option, and not at all costly if you only do that rarely (e.g. once at login). – Dan Sanderson Sep 09 '14 at 07:58
  • The above method would allow me to have only one user with some specific username, but when adding a new user i would still need to check that no other user has this username, otherwise it would just update that data members of user. – Gaurav Saini Sep 09 '14 at 17:34
  • Correct, but you have to do that regardless: you wouldn't blindly create a `User` without checking for an unused username first. Putting the username in the key makes it easy to do the existence check and the create in one transaction. This prevents clobbering an existing user even in the case when two users try to create an account with the same username concurrently. Your question was about fetching a `User` by a unique username, and fetching by key is faster and more amenable to transactions than querying a username property. – Dan Sanderson Sep 09 '14 at 19:40
0

How exactly do you create the user instance? The right way to do so is to say:

newUser = User(id=your_id)

You don't need to use newUser.id at all, it won't work. If you create a new user with an existing ID, it will replace the old one in the database. You must either find a way to keep track of the IDs you've used or give them different parent keys, in which case you can duplicate ID's if the parents are different.

Regarding your second question, I have read the get_by_id() class and what it actually does is that it uses a query. However if you’re gonna use the query yourself you must be sure to fetch 1 instance and get the first element in from the list returns by fetch() (User.query().fetch(1)[0]). It’s much easier to either use get_by_id() or, if you have the key, use key.get().

Y2H
  • 2,419
  • 1
  • 19
  • 37