Source code for sgcache.api3.create
import datetime
import sqlalchemy as sa
[docs]class Api3CreateOperation(object):
"""Operation to process an API3-style "create" request, which look like::
{
"fields": [
{
"field_name": "key",
"value": "value"
}
],
"return_fields": [
"id"
],
"type": "Test"
}
:param dict request: The request with ``fields``, ``return_fields``, and ``type``.
:param bool create_with_id: If an ID is provided, and the entity does not
exist, are we permitted to create that specific entity?
:raises ValueError: if an ID is given but ``create_with_id`` is not true.
"""
def __init__(self, request, create_with_id=False, source_event=None):
self.entity_type_name = request['type']
self.data = {x['field_name']: x['value'] for x in request['fields']}
self.return_fields = request['return_fields']
#: Did the entity exist? Set to ``True`` once run if an ID was provided
#: and that entity did exist.
self.entity_exists = None
self.entity_id = self.data.get('id') # this is for field.prepare_upsert_data
if self.entity_id and not create_with_id:
raise ValueError('cannot specify ID for create')
#: List of functions to call with the transaction before the primary query is
#: executed; generally appended to by the ``multi_entity`` :class:`~sgcache.fields.Base`.
self.before_query = []
#: List of functions to call with the transaction after the primary query is
#: executed; generally appended to by the ``multi_entity`` :class:`~sgcache.fields.Base`.
self.after_query = []
#: The :class:`~sgevents.event.Event` that triggered this query.
self.source_event = source_event
[docs] def run(self, cache, con=None, extra=None):
"""Run the create operation.
:param cache: The :class:`Cache`.
:param con: A SQLA connection; we will create one if not passed.
:param dict extra: Extra data to insert/update in the entity; used
for entity-level caching mechanisms.
"""
query_params = (extra or {}).copy()
# Manually deal with _active field, since it isn't actually a field
# in the cache and so won't be handled by below.
explicit_active = '_active' in self.data
query_params['_active'] = self.data.get('_active', True)
# Stuff in the current time for created/updated_at; the creation time
# will be popped out if we determine that the entity already exists.
query_params['_cache_created_at'] = query_params['_cache_updated_at'] = datetime.datetime.utcnow()
for field_name, field in cache[self.entity_type_name].fields.iteritems():
if field_name not in self.data:
continue
value = self.data[field_name]
field_params = field.prepare_upsert_data(self, value)
if field_params:
query_params.update(field_params)
with cache.db_begin(con) as con:
table = cache[self.entity_type_name].table
# Determine if the entity exists (to determine if we need to
# update or insert, but also for multi_entity fields to know
# if they need to do pre-processing).
if self.entity_id:
existing_row = con.execute(sa.select([table.c._last_log_event_id, table.c.updated_at]).where(table.c.id == self.entity_id).limit(1)).fetchone()
self.entity_exists = existing_row is not None
# Callbacks for multi_entity fields.
for func in self.before_query:
func(con)
if self.entity_exists:
# Do not update these fields:
del query_params['id']
del query_params['_cache_created_at']
# Allow _active during update if it was explicitly passed (e.g.
# by something that is (un)retiring an entity at the same time
# as creating/updating it).
if not explicit_active:
del query_params['_active']
# _last_log_event_id and updated_at should be the max of the existing
# and new values. This can cause problems with updated_by not
# matching up until the next scan, but *shrugs*.
for name in ('_last_log_event_id', 'updated_at'):
try:
v1 = query_params[name]
v2 = existing_row[name]
except KeyError:
continue
if v1 and v2:
query_params[name] = max(v1, v2)
# Update!
con.execute(table.update().where(table.c.id == self.entity_id), **query_params)
else:
# Insert!
res = con.execute(table.insert(), **query_params)
self.entity_id = self.entity_id or res.inserted_primary_key[0]
# Callbacks for multi_entity fields.
for func in self.after_query:
func(con)
return {'type': self.entity_type_name, 'id': self.entity_id}