# -*- coding: utf-8 -*- import base64 import cgi import copy import csv import datetime import decimal import os import shutil import sys import types import re from collections import OrderedDict from ._compat import ( PY2, StringIO, BytesIO, pjoin, exists, hashlib_md5, basestring, iteritems, xrange, implements_iterator, implements_bool, copyreg, reduce, to_bytes, to_native, long ) from ._globals import DEFAULT, IDENTITY, AND, OR from ._gae import Key from .exceptions import NotFoundException, NotAuthorizedException from .helpers.regex import ( REGEX_TABLE_DOT_FIELD, REGEX_ALPHANUMERIC, REGEX_PYTHON_KEYWORDS, REGEX_STORE_PATTERN, REGEX_UPLOAD_PATTERN, REGEX_CLEANUP_FN, REGEX_VALID_TB_FLD, REGEX_TYPE ) from .helpers.classes import ( Reference, MethodAdder, SQLCallableList, SQLALL, Serializable, BasicStorage, SQLCustomType, OpRow, cachedprop ) from .helpers.methods import ( list_represent, bar_decode_integer, bar_decode_string, bar_encode, archive_record, cleanup, use_common_filters, pluralize, attempt_upload_on_insert, attempt_upload_on_update, delete_uploaded_files ) from .helpers.serializers import serializers from .utils import deprecated DEFAULTLENGTH = {'string': 512, 'password': 512, 'upload': 512, 'text': 2**15, 'blob': 2**31} DEFAULT_REGEX = { 'id': '[1-9]\d*', 'decimal': '\d{1,10}\.\d{2}', 'integer': '[+-]?\d*', 'float': '[+-]?\d*(\.\d*)?', 'double': '[+-]?\d*(\.\d*)?', 'date': '\d{4}\-\d{2}\-\d{2}', 'time': '\d{2}\:\d{2}(\:\d{2}(\.\d*)?)?', 'datetime':'\d{4}\-\d{2}\-\d{2} \d{2}\:\d{2}(\:\d{2}(\.\d*)?)?', } class Row(BasicStorage): """ A dictionary that lets you do d['a'] as well as d.a this is only used to store a `Row` """ def __getitem__(self, k): key = str(k) _extra = super(Row, self).get('_extra', None) if _extra is not None: v = _extra.get(key, DEFAULT) if v != DEFAULT: return v try: return BasicStorage.__getattribute__(self, key) except AttributeError: pass m = REGEX_TABLE_DOT_FIELD.match(key) if m: try: e = super(Row, self).__getitem__(m.group(1)) return e[m.group(2)] except (KeyError, TypeError): pass key = m.group(2) try: return super(Row, self).__getitem__(key) except KeyError: pass try: e = super(Row, self).get('__get_lazy_reference__') if e is not None and callable(e): self[key] = e(key) return self[key] except Exception as e: raise e raise KeyError(key) __str__ = __repr__ = lambda self: '' % \ self.as_dict(custom_types=[LazySet]) __int__ = lambda self: self.get('id') __long__ = lambda self: long(self.get('id')) __call__ = __getitem__ def __getattr__(self, k): try: return self.__getitem__(k) except KeyError: raise AttributeError def __copy__(self): return Row(self) def __eq__(self, other): try: return self.as_dict() == other.as_dict() except AttributeError: return False def get(self, key, default=None): try: return self.__getitem__(key) except(KeyError, AttributeError, TypeError): return default def as_dict(self, datetime_to_str=False, custom_types=None): SERIALIZABLE_TYPES = [str, int, float, bool, list, dict] DT_INST = (datetime.date, datetime.datetime, datetime.time) if PY2: SERIALIZABLE_TYPES += [unicode, long] if isinstance(custom_types, (list, tuple, set)): SERIALIZABLE_TYPES += custom_types elif custom_types: SERIALIZABLE_TYPES.append(custom_types) d = dict(self) for k in list(d.keys()): v = d[k] if d[k] is None: continue elif isinstance(v, Row): d[k] = v.as_dict() elif isinstance(v, Reference): d[k] = long(v) elif isinstance(v, decimal.Decimal): d[k] = float(v) elif isinstance(v, DT_INST): if datetime_to_str: d[k] = v.isoformat().replace('T', ' ')[:19] elif not isinstance(v, tuple(SERIALIZABLE_TYPES)): del d[k] return d def as_xml(self, row_name="row", colnames=None, indent=' '): def f(row, field, indent=' '): if isinstance(row, Row): spc = indent+' \n' items = [f(row[x], x, indent+' ') for x in row] return '%s<%s>\n%s\n%s' % ( indent, field, spc.join(item for item in items if item), indent, field) elif not callable(row): if REGEX_ALPHANUMERIC.match(field): return '%s<%s>%s' % (indent, field, row, field) else: return '%s%s' % \ (indent, field, row) else: return None return f(self, row_name, indent=indent) def as_json(self, mode="object", default=None, colnames=None, serialize=True, **kwargs): """ serializes the row to a JSON object kwargs are passed to .as_dict method only "object" mode supported `serialize = False` used by Rows.as_json TODO: return array mode with query column order mode and colnames are not implemented """ item = self.as_dict(**kwargs) if serialize: return serializers.json(item) else: return item def pickle_row(s): return Row, (dict(s), ) copyreg.pickle(Row, pickle_row) class Table(Serializable, BasicStorage): """ Represents a database table Example:: You can create a table as:: db = DAL(...) db.define_table('users', Field('name')) And then:: db.users.insert(name='me') # print db.users._insert(...) to see SQL db.users.drop() """ def __init__(self, db, tablename, *fields, **args): """ Initializes the table and performs checking on the provided fields. Each table will have automatically an 'id'. If a field is of type Table, the fields (excluding 'id') from that table will be used instead. Raises: SyntaxError: when a supplied field is of incorrect type. """ # import DAL here to avoid circular imports from .base import DAL super(Table, self).__init__() self._actual = False # set to True by define_table() self._db = db self._migrate = None self._tablename = self._dalname = tablename if not isinstance(tablename, str) or hasattr(DAL, tablename) or not \ REGEX_VALID_TB_FLD.match(tablename) or \ REGEX_PYTHON_KEYWORDS.match(tablename): raise SyntaxError('Field: invalid table name: %s, ' 'use rname for "funny" names' % tablename) self._rname = args.get('rname') or \ db and db._adapter.dialect.quote(tablename) self._raw_rname = args.get('rname') or db and tablename self._sequence_name = args.get('sequence_name') or \ db and db._adapter.dialect.sequence_name(self._raw_rname) self._trigger_name = args.get('trigger_name') or \ db and db._adapter.dialect.trigger_name(tablename) self._common_filter = args.get('common_filter') self._format = args.get('format') self._singular = args.get( 'singular', tablename.replace('_', ' ').capitalize()) self._plural = args.get( 'plural', pluralize(self._singular.lower()).capitalize()) # horrible but for backard compatibility of appamdin: if 'primarykey' in args and args['primarykey'] is not None: self._primarykey = args.get('primarykey') self._before_insert = [attempt_upload_on_insert(self)] self._before_update = [ delete_uploaded_files, attempt_upload_on_update(self)] self._before_delete = [delete_uploaded_files] self._after_insert = [] self._after_update = [] self._after_delete = [] self._virtual_fields = [] self._virtual_methods = [] self.add_method = MethodAdder(self) fieldnames, newfields = set(), [] _primarykey = getattr(self, '_primarykey', None) if _primarykey is not None: if not isinstance(_primarykey, list): raise SyntaxError( "primarykey must be a list of fields from table '%s'" % tablename) if len(_primarykey) == 1: self._id = [ f for f in fields if isinstance(f, Field) and f.name == _primarykey[0]][0] elif not [f for f in fields if (isinstance(f, Field) and f.type == 'id') or (isinstance(f, dict) and f.get("type", None) == "id")]: field = Field('id', 'id') newfields.append(field) fieldnames.add('id') self._id = field virtual_fields = [] def include_new(field): newfields.append(field) fieldnames.add(field.name) if field.type == 'id': self._id = field for field in fields: if isinstance(field, (FieldVirtual, FieldMethod)): virtual_fields.append(field) elif isinstance(field, Field) and field.name not in fieldnames: if field.db is not None: field = copy.copy(field) include_new(field) elif isinstance(field, Table): table = field for field in table: if field.name not in fieldnames and field.type != 'id': t2 = not table._actual and self._tablename include_new(field.clone(point_self_references_to=t2)) elif isinstance(field, dict) and \ field['fieldname'] not in fieldnames: include_new(Field(**field)) elif not isinstance(field, (Field, Table)): raise SyntaxError( 'define_table argument is not a Field or Table: %s' % field ) fields = newfields tablename = tablename self._fields = SQLCallableList() self.virtualfields = [] fields = list(fields) if db and db._adapter.uploads_in_blob is True: uploadfields = [f.name for f in fields if f.type == 'blob'] for field in fields: fn = field.uploadfield if isinstance(field, Field) and field.type == 'upload'\ and fn is True and not field.uploadfs: fn = field.uploadfield = '%s_blob' % field.name if isinstance(fn, str) and fn not in uploadfields and \ not field.uploadfs: fields.append(Field(fn, 'blob', default='', writable=False, readable=False)) fieldnames_set = set() reserved = dir(Table) + ['fields'] if (db and db._check_reserved): check_reserved_keyword = db.check_reserved_keyword else: def check_reserved_keyword(field_name): if field_name in reserved: raise SyntaxError("field name %s not allowed" % field_name) for field in fields: field_name = field.name check_reserved_keyword(field_name) if db and db._ignore_field_case: fname_item = field_name.lower() else: fname_item = field_name if fname_item in fieldnames_set: raise SyntaxError( "duplicate field %s in table %s" % (field_name, tablename)) else: fieldnames_set.add(fname_item) self.fields.append(field_name) self[field_name] = field if field.type == 'id': self['id'] = field field.bind(self) self.ALL = SQLALL(self) if _primarykey is not None: for k in _primarykey: if k not in self.fields: raise SyntaxError( "primarykey must be a list of fields from table '%s " % tablename) else: self[k].notnull = True for field in virtual_fields: self[field.name] = field @property def fields(self): return self._fields def _structure(self): keys = ['name','type','writable','listable','searchable','regex','options', 'default','label','unique','notnull','required'] def noncallable(obj): return obj if not callable(obj) else None return [{key: noncallable(getattr(field, key)) for key in keys} for field in self if field.readable and not field.type=='password'] @cachedprop def _upload_fieldnames(self): return set(field.name for field in self if field.type == 'upload') def update(self, *args, **kwargs): raise RuntimeError("Syntax Not Supported") def _enable_record_versioning(self, archive_db=None, archive_name='%(tablename)s_archive', is_active='is_active', current_record='current_record', current_record_label=None, migrate=None, redefine=None): db = self._db archive_db = archive_db or db archive_name = archive_name % dict(tablename=self._dalname) if archive_name in archive_db.tables(): return # do not try define the archive if already exists fieldnames = self.fields() same_db = archive_db is db field_type = self if same_db else 'bigint' clones = [] for field in self: nfk = same_db or not field.type.startswith('reference') clones.append( field.clone(unique=False, type=field.type if nfk else 'bigint') ) d = dict(format=self._format) if migrate: d['migrate'] = migrate elif isinstance(self._migrate, basestring): d['migrate'] = self._migrate+'_archive' elif self._migrate: d['migrate'] = self._migrate if redefine: d['redefine'] = redefine archive_db.define_table( archive_name, Field(current_record, field_type, label=current_record_label), *clones, **d) self._before_update.append( lambda qset, fs, db=archive_db, an=archive_name, cn=current_record: archive_record(qset, fs, db[an], cn)) if is_active and is_active in fieldnames: self._before_delete.append( lambda qset: qset.update(is_active=False)) newquery = lambda query, t=self, name=self._tablename: reduce( AND, [ tab.is_active == True for tab in db._adapter.tables(query).values() if tab._raw_rname == self._raw_rname] ) query = self._common_filter if query: self._common_filter = lambda q: reduce( AND, [query(q), newquery(q)]) else: self._common_filter = newquery def _validate(self, **vars): errors = Row() for key, value in iteritems(vars): value, error = self[key].validate(value) if error: errors[key] = error return errors def _create_references(self): db = self._db pr = db._pending_references self._referenced_by_list = [] self._referenced_by = [] self._references = [] for field in self: # fieldname = field.name #FIXME not used ? field_type = field.type if isinstance(field_type, str) and ( field_type.startswith('reference ') or field_type.startswith('list:reference ')): is_list = field_type[:15] == 'list:reference ' if is_list: ref = field_type[15:].strip() else: ref = field_type[10:].strip() if not ref: SyntaxError('Table: reference to nothing: %s' % ref) if '.' in ref: rtablename, throw_it, rfieldname = ref.partition('.') else: rtablename, rfieldname = ref, None if rtablename not in db: pr[rtablename] = pr.get(rtablename, []) + [field] continue rtable = db[rtablename] if rfieldname: if not hasattr(rtable, '_primarykey'): raise SyntaxError( 'keyed tables can only reference other keyed tables (for now)') if rfieldname not in rtable.fields: raise SyntaxError( "invalid field '%s' for referenced table '%s'" " in table '%s'" % (rfieldname, rtablename, self._tablename) ) rfield = rtable[rfieldname] else: rfield = rtable._id if is_list: rtable._referenced_by_list.append(field) else: rtable._referenced_by.append(field) field.referent = rfield self._references.append(field) else: field.referent = None if self._tablename in pr: referees = pr.pop(self._tablename) for referee in referees: if referee.type.startswith('list:reference '): self._referenced_by_list.append(referee) else: self._referenced_by.append(referee) def _filter_fields(self, record, id=False): return dict([(k, v) for (k, v) in iteritems(record) if k in self.fields and (self[k].type != 'id' or id)]) def _build_query(self, key): """ for keyed table only """ query = None for k, v in iteritems(key): if k in self._primarykey: if query: query = query & (self[k] == v) else: query = (self[k] == v) else: raise SyntaxError( 'Field %s is not part of the primary key of %s' % (k, self._tablename)) return query def __getitem__(self, key): if not key: return None elif isinstance(key, dict): """ for keyed table """ query = self._build_query(key) return self._db(query).select( limitby=(0, 1), orderby_on_limitby=False ).first() else: try: isgoogle = 'google' in self._db._drivers_available and \ isinstance(key, Key) except: isgoogle = False if str(key).isdigit() or isgoogle: return self._db(self._id == key).select( limitby=(0, 1), orderby_on_limitby=False ).first() else: try: return getattr(self, key) except: raise KeyError(key) def __call__(self, key=DEFAULT, **kwargs): for_update = kwargs.get('_for_update', False) if '_for_update' in kwargs: del kwargs['_for_update'] orderby = kwargs.get('_orderby', None) if '_orderby' in kwargs: del kwargs['_orderby'] if key is not DEFAULT: if isinstance(key, Query): record = self._db(key).select( limitby=(0, 1), for_update=for_update, orderby=orderby, orderby_on_limitby=False).first() elif not str(key).isdigit(): record = None else: record = self._db(self._id == key).select( limitby=(0, 1), for_update=for_update, orderby=orderby, orderby_on_limitby=False).first() if record: for k, v in iteritems(kwargs): if record[k] != v: return None return record elif kwargs: query = reduce(lambda a, b: a & b, [ self[k] == v for k, v in iteritems(kwargs)]) return self._db(query).select(limitby=(0, 1), for_update=for_update, orderby=orderby, orderby_on_limitby=False).first() else: return None def __setitem__(self, key, value): if isinstance(key, dict) and isinstance(value, dict): """ option for keyed table """ if set(key.keys()) == set(self._primarykey): value = self._filter_fields(value) kv = {} kv.update(value) kv.update(key) if not self.insert(**kv): query = self._build_query(key) self._db(query).update(**self._filter_fields(value)) else: raise SyntaxError( 'key must have all fields from primary key: %s' % self._primarykey) elif str(key).isdigit(): if key == 0: self.insert(**self._filter_fields(value)) elif self._db(self._id == key)\ .update(**self._filter_fields(value)) is None: raise SyntaxError('No such record: %s' % key) else: if isinstance(key, dict): raise SyntaxError( 'value must be a dictionary: %s' % value) self.__dict__[str(key)] = value if isinstance(value, (FieldVirtual, FieldMethod)): if value.name == 'unknown': value.name = str(key) if isinstance(value, FieldVirtual): self._virtual_fields.append(value) else: self._virtual_methods.append(value) def __setattr__(self, key, value): if key[:1] != '_' and key in self: raise SyntaxError( 'Object exists and cannot be redefined: %s' % key) self[key] = value def __delitem__(self, key): if isinstance(key, dict): query = self._build_query(key) if not self._db(query).delete(): raise SyntaxError('No such record: %s' % key) elif not str(key).isdigit() or \ not self._db(self._id == key).delete(): raise SyntaxError('No such record: %s' % key) def __iter__(self): for fieldname in self.fields: yield self[fieldname] def __repr__(self): return '' % (self._tablename, ', '.join(self.fields())) def __str__(self): if self._tablename == self._dalname: return self._tablename return self._db._adapter.dialect._as(self._dalname, self._tablename) @property @deprecated('sqlsafe', 'sql_shortref', 'Table') def sqlsafe(self): return self.sql_shortref @property @deprecated('sqlsafe_alias', 'sql_fullref', 'Table') def sqlsafe_alias(self): return self.sql_fullref @property def sql_shortref(self): if self._tablename == self._dalname: return self._rname return self._db._adapter.sqlsafe_table(self._tablename) @property def sql_fullref(self): if self._tablename == self._dalname: return self._rname return self._db._adapter.sqlsafe_table(self._tablename, self._rname) def query_name(self, *args, **kwargs): return (self.sql_fullref,) def _drop(self, mode=''): return self._db._adapter.dialect.drop_table(self, mode) def drop(self, mode=''): return self._db._adapter.drop_table(self, mode) def _filter_fields_for_operation(self, fields): new_fields = {} # format: new_fields[name] = (field, value) input_fieldnames = set(fields) table_fieldnames = set(self.fields) empty_fieldnames = OrderedDict((name, name) for name in self.fields) for name in list(input_fieldnames & table_fieldnames): field = self[name] value = field.filter_in(fields[name]) \ if field.filter_in else fields[name] new_fields[name] = (field, value) del empty_fieldnames[name] return list(empty_fieldnames), new_fields def _compute_fields_for_operation(self, fields, to_compute): row = OpRow(self) for name, tup in iteritems(fields): field, value = tup if isinstance( value, ( types.LambdaType, types.FunctionType, types.MethodType, types.BuiltinFunctionType, types.BuiltinMethodType ) ): value = value() row.set_value(name, value, field) for name, field in to_compute: try: row.set_value(name, field.compute(row), field) except (KeyError, AttributeError): # error silently unless field is required! if field.required and name not in fields: raise RuntimeError( 'unable to compute required field: %s' % name) return row def _fields_and_values_for_insert(self, fields): empty_fieldnames, new_fields = \ self._filter_fields_for_operation(fields) to_compute = [] for name in empty_fieldnames: field = self[name] if field.compute: to_compute.append((name, field)) elif field.default is not None: new_fields[name] = (field, field.default) elif field.required: raise RuntimeError( 'Table: missing required field: %s' % name) return self._compute_fields_for_operation( new_fields, to_compute) def _fields_and_values_for_update(self, fields): empty_fieldnames, new_fields = \ self._filter_fields_for_operation(fields) to_compute = [] for name in empty_fieldnames: field = self[name] if field.compute: to_compute.append((name, field)) if field.update is not None: new_fields[name] = (field, field.update) return self._compute_fields_for_operation( new_fields, to_compute) def _insert(self, **fields): row = self._fields_and_values_for_insert(fields) return self._db._adapter._insert(self, row.op_values()) def insert(self, **fields): row = self._fields_and_values_for_insert(fields) if any(f(row) for f in self._before_insert): return 0 ret = self._db._adapter.insert(self, row.op_values()) if ret and self._after_insert: for f in self._after_insert: f(row, ret) return ret def _validate_fields(self, fields, defattr='default'): response = Row() response.id, response.errors, new_fields = None, Row(), Row() for field in self: # we validate even if not passed in case it is required error = default = None if not field.required and not field.compute: default = getattr(field, defattr) if callable(default): default = default() if not field.compute: value = fields.get(field.name, default) value, error = field.validate(value) if error: response.errors[field.name] = "%s" % error elif field.name in fields: # only write if the field was passed and no error new_fields[field.name] = value return response, new_fields def validate_and_insert(self, **fields): response, new_fields = self._validate_fields(fields, 'default') if not response.errors: response.id = self.insert(**new_fields) return response def validate_and_update(self, _key=DEFAULT, **fields): response, new_fields = self._validate_fields(fields, 'update') #: select record(s) for update if _key is DEFAULT: record = self(**fields) elif isinstance(_key, dict): record = self(**_key) else: record = self(_key) #: do the update if not response.errors and record: if '_id' in self: myset = self._db(self._id == record[self._id.name]) else: query = None for key, value in iteritems(_key): if query is None: query = getattr(self, key) == value else: query = query & (getattr(self, key) == value) myset = self._db(query) response.id = myset.update(**new_fields) and record[self._id.name] return response def update_or_insert(self, _key=DEFAULT, **values): if _key is DEFAULT: record = self(**values) elif isinstance(_key, dict): record = self(**_key) else: record = self(_key) if record: record.update_record(**values) newid = None else: newid = self.insert(**values) return newid def validate_and_update_or_insert(self, _key=DEFAULT, **fields): if _key is DEFAULT or _key == '': primary_keys = {} for key, value in iteritems(fields): if key in self._primarykey: primary_keys[key] = value if primary_keys != {}: record = self(**primary_keys) _key = primary_keys else: required_keys = {} for key, value in iteritems(fields): if getattr(self, key).required: required_keys[key] = value record = self(**required_keys) _key = required_keys elif isinstance(_key, dict): record = self(**_key) else: record = self(_key) if record: response = self.validate_and_update(_key, **fields) if hasattr(self, '_primarykey'): primary_keys = {} for key in self._primarykey: primary_keys[key] = getattr(record, key) response.id = primary_keys else: response = self.validate_and_insert(**fields) return response def bulk_insert(self, items): """ here items is a list of dictionaries """ data = [self._fields_and_values_for_insert(item) for item in items] if any(f(el) for el in data for f in self._before_insert): return 0 ret = self._db._adapter.bulk_insert( self, [el.op_values() for el in data]) ret and [ [f(el, ret[k]) for k, el in enumerate(data)] for f in self._after_insert] return ret def _truncate(self, mode=''): return self._db._adapter.dialect.truncate(self, mode) def truncate(self, mode=''): return self._db._adapter.truncate(self, mode) def import_from_csv_file(self, csvfile, id_map = None, null = '', unique = 'uuid', id_offset = None, # id_offset used only when id_map is None transform = None, validate=False, **kwargs ): """ Import records from csv file. Column headers must have same names as table fields. Field 'id' is ignored. If column names read 'table.file' the 'table.' prefix is ignored. - 'unique' argument is a field which must be unique (typically a uuid field) - 'restore' argument is default False; if set True will remove old values in table first. - 'id_map' if set to None will not map ids The import will keep the id numbers in the restored table. This assumes that there is an field of type id that is integer and in incrementing order. Will keep the id numbers in restored table. """ if validate: inserting=self.validate_and_insert else: inserting=self.insert delimiter = kwargs.get('delimiter', ',') quotechar = kwargs.get('quotechar', '"') quoting = kwargs.get('quoting', csv.QUOTE_MINIMAL) restore = kwargs.get('restore', False) if restore: self._db[self].truncate() reader = csv.reader(csvfile, delimiter=delimiter, quotechar=quotechar, quoting=quoting) colnames = None if isinstance(id_map, dict): if self._tablename not in id_map: id_map[self._tablename] = {} id_map_self = id_map[self._tablename] def fix(field, value, id_map, id_offset): list_reference_s = 'list:reference' if value == null: value = None elif field.type == 'blob': value = base64.b64decode(value) elif field.type == 'double' or field.type == 'float': if not value.strip(): value = None else: value = float(value) elif field.type in ('integer', 'bigint'): if not value.strip(): value = None else: value = long(value) elif field.type.startswith('list:string'): value = bar_decode_string(value) elif field.type.startswith(list_reference_s): ref_table = field.type[len(list_reference_s):].strip() if id_map is not None: value = [id_map[ref_table][long(v)] for v in bar_decode_string(value)] else: value = [v for v in bar_decode_string(value)] elif field.type.startswith('list:'): value = bar_decode_integer(value) elif id_map and field.type.startswith('reference'): try: value = id_map[field.type[9:].strip()][long(value)] except KeyError: pass elif id_offset and field.type.startswith('reference'): try: value = id_offset[field.type[9:].strip()]+long(value) except KeyError: pass return value def is_id(colname): if colname in self: return self[colname].type == 'id' else: return False first = True unique_idx = None for lineno, line in enumerate(reader): if not line: return if not colnames: # assume this is the first line of the input, contains colnames colnames = [x.split('.', 1)[-1] for x in line][:len(line)] cols, cid = {}, None for i, colname in enumerate(colnames): if is_id(colname): cid = colname elif colname in self.fields: cols[colname] = self[colname] if colname == unique: unique_idx = i elif len(line)==len(colnames): # every other line contains instead data items = dict(zip(colnames, line)) if transform: items = transform(items) ditems = dict() csv_id = None for field in self: fieldname = field.name if fieldname in items: try: value = fix(field, items[fieldname], id_map, id_offset) if field.type!='id': ditems[fieldname] = value else: csv_id = long(value) except ValueError: raise RuntimeError("Unable to parse line:%s" % (lineno+1)) if not (id_map or csv_id is None or id_offset is None or unique_idx): curr_id = inserting(**ditems) if first: first = False # First curr_id is bigger than csv_id, # then we are not restoring but # extending db table with csv db table id_offset[self._tablename] = (curr_id-csv_id) \ if curr_id > csv_id else 0 # create new id until we get the same as old_id+offset while curr_id < csv_id+id_offset[self._tablename]: self._db(self[cid] == curr_id).delete() curr_id = inserting(**ditems) # Validation. Check for duplicate of 'unique' &, # if present, update instead of insert. elif not unique_idx: new_id = inserting(**ditems) else: unique_value = line[unique_idx] query = self[unique] == unique_value record = self._db(query).select().first() if record: record.update_record(**ditems) new_id = record[self._id.name] else: new_id = inserting(**ditems) if id_map and csv_id is not None: id_map_self[csv_id] = new_id if lineno % 1000 == 999: self._db.commit() def as_dict(self, flat=False, sanitize=True): table_as_dict = dict( tablename=str(self), fields=[], sequence_name=self._sequence_name, trigger_name=self._trigger_name, common_filter=self._common_filter, format=self._format, singular=self._singular, plural=self._plural) for field in self: if (field.readable or field.writable) or (not sanitize): table_as_dict["fields"].append(field.as_dict( flat=flat, sanitize=sanitize)) return table_as_dict def with_alias(self, alias): try: if self._db[alias]._rname == self._rname: return self._db[alias] except AttributeError: # we never used this alias pass other = copy.copy(self) other['ALL'] = SQLALL(other) other['_tablename'] = alias for fieldname in other.fields: tmp = self[fieldname].clone() tmp.bind(other) other[fieldname] = tmp if 'id' in self and 'id' not in other.fields: other['id'] = other[self.id.name] other._id = other[self._id.name] self._db[alias] = other return other def on(self, query): return Expression(self._db, self._db._adapter.dialect.on, self, query) def create_index(self, name, *fields, **kwargs): return self._db._adapter.create_index(self, name, *fields, **kwargs) def drop_index(self, name): return self._db._adapter.drop_index(self, name) class Select(BasicStorage): def __init__(self, db, query, fields, attributes): self._db = db self._tablename = None # alias will be stored here self._rname = self._raw_rname = self._dalname = None self._common_filter = None self._query = query # if false, the subquery will never reference tables from parent scope self._correlated = attributes.pop('correlated', True) self._attributes = attributes self._qfields = list(fields) self._fields = SQLCallableList() self._virtual_fields = [] self._virtual_methods = [] self.virtualfields = [] self._sql_cache = None self._colnames_cache = None fieldcheck = set() for item in fields: if isinstance(item, Field): checkname = item.name field = item.clone() elif isinstance(item, Expression): if item.op != item._dialect._as: continue checkname = item.second field = Field(item.second, type=item.type) else: raise SyntaxError('Invalid field in Select') if db and db._ignore_field_case: checkname = checkname.lower() if checkname in fieldcheck: raise SyntaxError("duplicate field %s in select query" % field.name) fieldcheck.add(checkname) field.bind(self) self.fields.append(field.name) self[field.name] = field self.ALL = SQLALL(self) @property def fields(self): return self._fields def update(self, *args, **kwargs): raise RuntimeError("update() method not supported") def __getitem__(self, key): try: return getattr(self, key) except AttributeError: raise KeyError(key) def __setitem__(self, key, value): self.__dict__[str(key)] = value def __call__(self): adapter = self._db._adapter colnames, sql = self._compile() cache = self._attributes.get('cache', None) if cache and self._attributes.get('cacheable', False): return adapter._cached_select(cache, sql, self._fields, self._attributes, colnames) return adapter._select_aux(sql, self._qfields, self._attributes, colnames) def __setattr__(self, key, value): if key[:1] != '_' and key in self: raise SyntaxError( 'Object exists and cannot be redefined: %s' % key) self[key] = value def __iter__(self): for fieldname in self.fields: yield self[fieldname] def __repr__(self): return '