def push(engine: Engine, id: str) -> Tuple[int, int]: """Returns (status, position) tuple. next_position should be a guess; always guess low, or misordering can occur. Position will be the reserved queue position, even if the user already existed.""" # -1. Non-authoritative check for existing user. # This doesn't guarantee the user doesn't exist, it just saves a lot of empty queue positions # when a malicious actor tries to enqueue themselves many times. # (Mitigation factor depends on ) try: existing = engine.query(Person, key=Person.id == id).first() # If we don't hit an exception, person already exists return 409, existing.position except ConstraintViolation: # Probably doesn't exist, keep going pass # 0. Reserve a row in QueueEntry with the next queue position. # Don't insert the id until we confirm that this user # hasn't already been enqueued. entry = QueueEntry(position=0, enqueued_at=pendulum.now(), served_at=None) while True: try: engine.save(entry, condition=QueueEntry.NOT_EXIST) except ConstraintViolation: # TODO | can be much faster: # TODO | - use a binary walk from 0 until we hit a missing, # TODO | - then binary walk back to an existing, # TODO | - then use try save with increment of 1 entry.position += 1 else: break # 1. Now that we've reserved a row, try to build the id -> position mapping. # Note that a failure here means the user is already queued, so we abort. # Can't roll back the QueueEntry though, since another caller may have already # advanced past us. person = Person(id=id, position=entry.position) try: engine.save(person, condition=Person.NOT_ENQUEUED) except ConstraintViolation: return 409, entry.position # 2. Success! Finally, push the id into the QueueEntry. # It's ok if this fails, because we can always rebuild the id from the Person table. entry.id = id try: engine.save(entry) except BloopException: # Can't be sure the entry is saved, but it's fine because we'll # be able to rebuild from the Person return 404, entry.position return 200, entry.position
def serve(engine: Engine, id: str) -> int: """200 success, 404 missing row, 409 already served""" # 0. Find the QueueEntry by Person person = Person(id=id) try: engine.load(person) except MissingObjects: return 404 entry = QueueEntry(position=person.position) try: engine.load(entry) except MissingObjects: return 404 # 1. Update the time the entry was served at. # If the entry was already served, don't change the time. entry.served_at = pendulum.now() try: engine.save(entry, condition=QueueEntry.served_at.is_(None)) except ConstraintViolation: # Already served, no change return 409 else: return 200
class Item(BaseModel): id = Column(UUID, hash_key=True) data = Column(Product) engine = Engine() engine.bind(BaseModel) # ================================================ # Usage # ================================================ item = Item(id=uuid.uuid4()) item.data = { 'Name': 'item-name', 'Rating': decimal.Decimal(str(random.random())), 'Updated': datetime.now(timezone.utc), 'Description': { 'Title': 'item-title', 'Body': 'item-body', }, 'Sellers': set() } for i in range(4): seller_id = 'seller-{}'.format(i) item.data['Sellers'].add(seller_id) engine.save(item)
class Tweet(engine.model): class Meta: write_units = 10 account = Column(UUID, hash_key=True) id = Column(String, range_key=True) content = Column(String) date = Column(DateTime(timezone='EU/Paris')) favorites = Column(Integer) by_date = GlobalSecondaryIndex( hash_key='date', projection='keys_only') engine.bind() # ================================================ # Usage # ================================================ account = Account( id=uuid.uuid4(), name='@garybernhardt', email='REDACTED') tweet = Tweet( account=account.id, id='616102582239399936', content='today, I wrote a type validator in Python, as you do', favorites=9, date=arrow.now()) engine.save([account, tweet])
class Tweet(BaseModel): class Meta: write_units = 10 account = Column(UUID, hash_key=True) id = Column(String, range_key=True) content = Column(String) date = Column(DateTime) favorites = Column(Integer) by_date = GlobalSecondaryIndex(hash_key='date', projection='keys') engine = Engine() engine.bind(BaseModel) # ================================================ # Usage # ================================================ account = Account(id=uuid.uuid4(), name='@garybernhardt', email='REDACTED') tweet = Tweet(account=account.id, id='616102582239399936', content='today, I wrote a type validator in Python, as you do', favorites=9, date=datetime.now(timezone.utc)) engine.save(account, tweet)
}) class Item(engine.model): id = Column(UUID, hash_key=True) data = Column(Product) engine.bind() # ================================================ # Usage # ================================================ item = Item(id=uuid.uuid4()) item.data = { 'Name': 'item-name', 'Rating': decimal.Decimal(str(random.random())), 'Updated': arrow.now(), 'Description': { 'Title': 'item-title', 'Body': 'item-body', }, 'Sellers': {} } for i in range(4): seller_name = 'seller-{}'.format(i) item.data['Sellers'][seller_name] = random.randint(0, 100) engine.save(item)