Merge branch 'dictionary-db-defs'

Tries to fix issue 1639
This commit is contained in:
spametki
2013-09-03 19:41:48 -03:00
2 changed files with 117 additions and 194 deletions
+89 -165
View File
@@ -7258,7 +7258,7 @@ class DAL(object):
or or
db = DAL({"uri": ..., "items": ...}) # experimental db = DAL(**{"uri": ..., "tables": [...]...}) # experimental
db.define_table('tablename', Field('fieldname1'), db.define_table('tablename', Field('fieldname1'),
Field('fieldname2')) Field('fieldname2'))
@@ -7373,8 +7373,9 @@ class DAL(object):
migrate_enabled=True, fake_migrate_all=False, migrate_enabled=True, fake_migrate_all=False,
decode_credentials=False, driver_args=None, decode_credentials=False, driver_args=None,
adapter_args=None, attempts=5, auto_import=False, adapter_args=None, attempts=5, auto_import=False,
bigint_id=False,debug=False,lazy_tables=False, bigint_id=False, debug=False, lazy_tables=False,
db_uid=None, do_connect=True, after_connection=None): db_uid=None, do_connect=True,
after_connection=None, tables=None):
""" """
Creates a new Database Abstraction Layer instance. Creates a new Database Abstraction Layer instance.
@@ -7386,7 +7387,7 @@ class DAL(object):
experimental: you can specify a dictionary as uri experimental: you can specify a dictionary as uri
parameter i.e. with parameter i.e. with
db = DAL({"uri": "sqlite://storage.sqlite", db = DAL({"uri": "sqlite://storage.sqlite",
"items": {...}, ...}) "tables": {...}, ...})
for an example of dict input you can check the output for an example of dict input you can check the output
of the scaffolding db model with of the scaffolding db model with
@@ -7430,18 +7431,6 @@ class DAL(object):
:lazy_tables (defaults to False): delay table definition until table access :lazy_tables (defaults to False): delay table definition until table access
:after_connection (defaults to None): a callable that will be execute after the connection :after_connection (defaults to None): a callable that will be execute after the connection
""" """
items = None
if isinstance(uri, dict):
if "items" in uri:
items = uri.pop("items")
try:
newuri = uri.pop("uri")
except KeyError:
newuri = DEFAULT_URI
locals().update(uri)
uri = newuri
if uri == '<zombie>' and db_uid is not None: return if uri == '<zombie>' and db_uid is not None: return
if not decode_credentials: if not decode_credentials:
credential_decoder = lambda cred: cred credential_decoder = lambda cred: cred
@@ -7534,31 +7523,20 @@ class DAL(object):
self._fake_migrate = fake_migrate self._fake_migrate = fake_migrate
self._migrate_enabled = migrate_enabled self._migrate_enabled = migrate_enabled
self._fake_migrate_all = fake_migrate_all self._fake_migrate_all = fake_migrate_all
if auto_import or items: if auto_import or tables:
self.import_table_definitions(adapter.folder, self.import_table_definitions(adapter.folder,
items=items) tables=tables)
@property @property
def tables(self): def tables(self):
return self._tables return self._tables
def import_table_definitions(self, path, migrate=False, def import_table_definitions(self, path, migrate=False,
fake_migrate=False, items=None): fake_migrate=False, tables=None):
pattern = pjoin(path,self._uri_hash+'_*.table') pattern = pjoin(path,self._uri_hash+'_*.table')
if items: if tables:
for tablename, table in items.iteritems(): for table in tables:
# TODO: read all field/table options self.define_table(**table)
fields = []
# remove unsupported/illegal Table arguments
[table.pop(name) for name in ("name", "fields") if
name in table]
if "items" in table:
for fieldname, field in table.pop("items").iteritems():
# remove unsupported/illegal Field arguments
[field.pop(key) for key in ("requires", "name",
"compute", "colname") if key in field]
fields.append(Field(str(fieldname), **field))
self.define_table(str(tablename), *fields, **table)
else: else:
for filename in glob.glob(pattern): for filename in glob.glob(pattern):
tfile = self._adapter.file_open(filename, 'r') tfile = self._adapter.file_open(filename, 'r')
@@ -7856,8 +7834,14 @@ def index():
): ):
if not fields and 'fields' in args: if not fields and 'fields' in args:
fields = args.get('fields',()) fields = args.get('fields',())
if not isinstance(tablename,str): if not isinstance(tablename, str):
raise SyntaxError("missing table name") if isinstance(tablename, unicode):
try:
tablename = str(tablename)
except UnicodeEncodeError:
raise SyntaxError("invalid unicode table name")
else:
raise SyntaxError("missing table name")
elif hasattr(self,tablename) or tablename in self.tables: elif hasattr(self,tablename) or tablename in self.tables:
if not args.get('redefine',False): if not args.get('redefine',False):
raise SyntaxError('table already defined: %s' % tablename) raise SyntaxError('table already defined: %s' % tablename)
@@ -7921,48 +7905,40 @@ def index():
if on_define: on_define(table) if on_define: on_define(table)
return table return table
def as_dict(self, flat=False, sanitize=True, field_options=True): def as_dict(self, flat=False, sanitize=True):
dbname = db_uid = uri = None db_uid = uri = None
if not sanitize: if not sanitize:
uri, dbname, db_uid = (self._uri, self._dbname, self._db_uid) uri, db_uid = (self._uri, self._db_uid)
db_as_dict = dict(items={}, tables=[], uri=uri, dbname=dbname, db_as_dict = dict(tables=[], uri=uri, db_uid=db_uid,
db_uid=db_uid, **dict([(k, getattr(self, "_" + k, None))
**dict([(k, getattr(self, "_" + k)) for for k in 'pool_size','folder','db_codec',
k in 'pool_size','folder','db_codec',
'check_reserved','migrate','fake_migrate', 'check_reserved','migrate','fake_migrate',
'migrate_enabled','fake_migrate_all', 'migrate_enabled','fake_migrate_all',
'decode_credentials','driver_args', 'decode_credentials','driver_args',
'adapter_args', 'attempts', 'adapter_args', 'attempts',
'bigint_id','debug','lazy_tables', 'bigint_id','debug','lazy_tables',
'do_connect'])) 'do_connect']))
for table in self: for table in self:
tablename = str(table) db_as_dict["tables"].append(table.as_dict(flat=flat,
db_as_dict["tables"].append(tablename) sanitize=sanitize))
db_as_dict["items"][tablename] = table.as_dict(flat=flat,
sanitize=sanitize,
field_options=field_options)
return db_as_dict return db_as_dict
def as_xml(self, sanitize=True, field_options=True): def as_xml(self, sanitize=True):
if not have_serializers: if not have_serializers:
raise ImportError("No xml serializers available") raise ImportError("No xml serializers available")
d = self.as_dict(flat=True, sanitize=sanitize, d = self.as_dict(flat=True, sanitize=sanitize)
field_options=field_options)
return serializers.xml(d) return serializers.xml(d)
def as_json(self, sanitize=True, field_options=True): def as_json(self, sanitize=True):
if not have_serializers: if not have_serializers:
raise ImportError("No json serializers available") raise ImportError("No json serializers available")
d = self.as_dict(flat=True, sanitize=sanitize, d = self.as_dict(flat=True, sanitize=sanitize)
field_options=field_options)
return serializers.json(d) return serializers.json(d)
def as_yaml(self, sanitize=True, field_options=True): def as_yaml(self, sanitize=True):
if not have_serializers: if not have_serializers:
raise ImportError("No YAML serializers available") raise ImportError("No YAML serializers available")
d = self.as_dict(flat=True, sanitize=sanitize, d = self.as_dict(flat=True, sanitize=sanitize)
field_options=field_options)
return serializers.yaml(d) return serializers.yaml(d)
def __contains__(self, tablename): def __contains__(self, tablename):
@@ -8330,7 +8306,9 @@ class Table(object):
if len(self._primarykey)==1: if len(self._primarykey)==1:
self._id = [f for f in fields if isinstance(f,Field) \ self._id = [f for f in fields if isinstance(f,Field) \
and f.name==self._primarykey[0]][0] and f.name==self._primarykey[0]][0]
elif not [f for f in fields if isinstance(f,Field) and f.type=='id']: 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') field = Field('id', 'id')
newfields.append(field) newfields.append(field)
fieldnames.add('id') fieldnames.add('id')
@@ -8348,8 +8326,7 @@ class Table(object):
if field.db is not None: if field.db is not None:
field = copy.copy(field) field = copy.copy(field)
include_new(field) include_new(field)
elif isinstance(field, dict) and 'fieldname' and \ elif isinstance(field, dict) and not field['fieldname'] in fieldnames:
not field['fieldname'] in fieldnames:
include_new(Field(**field)) include_new(Field(**field))
elif isinstance(field, Table): elif isinstance(field, Table):
table = field table = field
@@ -8937,9 +8914,8 @@ class Table(object):
if id_map and cid is not None: if id_map and cid is not None:
id_map_self[long(line[cid])] = new_id id_map_self[long(line[cid])] = new_id
def as_dict(self, flat=False, sanitize=True, field_options=True): def as_dict(self, flat=False, sanitize=True):
tablename = str(self) table_as_dict = dict(tablename=str(self), fields=[],
table_as_dict = dict(name=tablename, items={}, fields=[],
sequence_name=self._sequence_name, sequence_name=self._sequence_name,
trigger_name=self._trigger_name, trigger_name=self._trigger_name,
common_filter=self._common_filter, format=self._format, common_filter=self._common_filter, format=self._format,
@@ -8947,31 +8923,26 @@ class Table(object):
for field in self: for field in self:
if (field.readable or field.writable) or (not sanitize): if (field.readable or field.writable) or (not sanitize):
table_as_dict["fields"].append(field.name) table_as_dict["fields"].append(field.as_dict(
table_as_dict["items"][field.name] = \ flat=flat, sanitize=sanitize))
field.as_dict(flat=flat, sanitize=sanitize,
options=field_options)
return table_as_dict return table_as_dict
def as_xml(self, sanitize=True, field_options=True): def as_xml(self, sanitize=True):
if not have_serializers: if not have_serializers:
raise ImportError("No xml serializers available") raise ImportError("No xml serializers available")
d = self.as_dict(flat=True, sanitize=sanitize, d = self.as_dict(flat=True, sanitize=sanitize)
field_options=field_options)
return serializers.xml(d) return serializers.xml(d)
def as_json(self, sanitize=True, field_options=True): def as_json(self, sanitize=True):
if not have_serializers: if not have_serializers:
raise ImportError("No json serializers available") raise ImportError("No json serializers available")
d = self.as_dict(flat=True, sanitize=sanitize, d = self.as_dict(flat=True, sanitize=sanitize)
field_options=field_options)
return serializers.json(d) return serializers.json(d)
def as_yaml(self, sanitize=True, field_options=True): def as_yaml(self, sanitize=True):
if not have_serializers: if not have_serializers:
raise ImportError("No YAML serializers available") raise ImportError("No YAML serializers available")
d = self.as_dict(flat=True, sanitize=sanitize, d = self.as_dict(flat=True, sanitize=sanitize)
field_options=field_options)
return serializers.yaml(d) return serializers.yaml(d)
def with_alias(self, alias): def with_alias(self, alias):
@@ -9452,8 +9423,13 @@ class Field(Expression):
self.op = None self.op = None
self.first = None self.first = None
self.second = None self.second = None
if isinstance(fieldname, unicode):
try:
fieldname = str(fieldname)
except UnicodeEncodeError:
raise SyntaxError('Field: invalid unicode field name')
self.name = fieldname = cleanup(fieldname) self.name = fieldname = cleanup(fieldname)
if not isinstance(fieldname,str) or hasattr(Table,fieldname) or \ if not isinstance(fieldname, str) or hasattr(Table, fieldname) or \
fieldname[0] == '_' or REGEX_PYTHON_KEYWORDS.match(fieldname): fieldname[0] == '_' or REGEX_PYTHON_KEYWORDS.match(fieldname):
raise SyntaxError('Field: invalid field name: %s' % fieldname) raise SyntaxError('Field: invalid field name: %s' % fieldname)
self.type = type if not isinstance(type, (Table,Field)) else 'reference %s' % type self.type = type if not isinstance(type, (Table,Field)) else 'reference %s' % type
@@ -9653,113 +9629,61 @@ class Field(Expression):
def count(self, distinct=None): def count(self, distinct=None):
return Expression(self.db, self.db._adapter.COUNT, self, distinct, 'integer') return Expression(self.db, self.db._adapter.COUNT, self, distinct, 'integer')
def as_dict(self, flat=False, sanitize=True, options=True): def as_dict(self, flat=False, sanitize=True):
attrs = ("name", 'authorize', 'represent', 'ondelete',
attrs = ('type', 'length', 'default', 'required', 'custom_store', 'autodelete', 'custom_retrieve',
'ondelete', 'notnull', 'unique', 'uploadfield', 'filter_out', 'uploadseparate', 'widget', 'uploadfs',
'widget', 'label', 'comment', 'writable', 'readable', 'update', 'custom_delete', 'uploadfield', 'uploadfolder',
'update', 'authorize', 'autodelete', 'represent', 'custom_qualifier', 'unique', 'writable', 'compute',
'uploadfolder', 'uploadseparate', 'uploadfs', 'map_none', 'default', 'type', 'required', 'readable',
'compute', 'custom_store', 'custom_retrieve', 'requires', 'comment', 'label', 'length', 'notnull',
'custom_retrieve_file_properties', 'custom_delete', 'custom_retrieve_file_properties', 'filter_in')
'filter_in', 'filter_out', 'custom_qualifier', serializable = (int, long, basestring, float, tuple,
'map_none', 'name') bool, type(None))
SERIALIZABLE_TYPES = (int, long, basestring, dict, list,
float, tuple, bool, type(None))
def flatten(obj): def flatten(obj):
if flat: if isinstance(obj, dict):
if isinstance(obj, flatten.__class__): return dict((flatten(k), flatten(v)) for k, v in
return str(type(obj)) obj.items())
elif isinstance(obj, type): elif isinstance(obj, (tuple, list, set)):
try: return [flatten(v) for v in obj]
return str(obj).split("'")[1] elif isinstance(obj, serializable):
except IndexError: return obj
return str(obj) elif isinstance(obj, (datetime.datetime,
elif not isinstance(obj, SERIALIZABLE_TYPES): datetime.date, datetime.time)):
return str(obj) return str(obj)
elif isinstance(obj, dict): else:
newobj = dict()
for k, v in obj.items():
newobj[k] = flatten(v)
return newobj
elif isinstance(obj, (list, tuple, set)):
return [flatten(v) for v in obj]
else:
return obj
elif isinstance(obj, (dict, set)):
return obj.copy()
else: return obj
def filter_requires(t, r, options=True):
if sanitize and any([keyword in str(t).upper() for
keyword in ("CRYPT", "IS_STRONG")]):
return None return None
if not isinstance(r, dict): d = dict()
if options and hasattr(r, "options"): if not (sanitize and not (self.readable or self.writable)):
if callable(r.options): for attr in attrs:
r.options() if flat:
newr = r.__dict__.copy() d.update({attr: flatten(getattr(self, attr))})
else: else:
newr = r.copy() d.update({attr: getattr(self, attr)})
d["fieldname"] = d.pop("name")
# remove options if not required
if not options and newr.has_key("labels"):
[newr.update({key:None}) for key in
("labels", "theset") if (key in newr)]
for k, v in newr.items():
if k == "other":
if isinstance(v, dict):
otype, other = v.popitem()
else:
otype = flatten(type(v))
other = v
newr[k] = {otype: filter_requires(otype, other,
options=options)}
else:
newr[k] = flatten(v)
return newr
if isinstance(self.requires, (tuple, list, set)):
requires = dict([(flatten(type(r)),
filter_requires(type(r), r,
options=options)) for
r in self.requires])
else:
requires = {flatten(type(self.requires)):
filter_requires(type(self.requires),
self.requires, options=options)}
d = dict(colname="%s.%s" % (self.tablename, self.name),
requires=requires)
d.update([(attr, flatten(getattr(self, attr))) for attr in attrs])
return d return d
def as_xml(self, sanitize=True, options=True): def as_xml(self, sanitize=True):
if have_serializers: if have_serializers:
xml = serializers.xml xml = serializers.xml
else: else:
raise ImportError("No xml serializers available") raise ImportError("No xml serializers available")
d = self.as_dict(flat=True, sanitize=sanitize, d = self.as_dict(flat=True, sanitize=sanitize)
options=options)
return xml(d) return xml(d)
def as_json(self, sanitize=True, options=True): def as_json(self, sanitize=True):
if have_serializers: if have_serializers:
json = serializers.json json = serializers.json
else: else:
raise ImportError("No json serializers available") raise ImportError("No json serializers available")
d = self.as_dict(flat=True, sanitize=sanitize, d = self.as_dict(flat=True, sanitize=sanitize)
options=options)
return json(d) return json(d)
def as_yaml(self, sanitize=True, options=True): def as_yaml(self, sanitize=True):
if have_serializers: if have_serializers:
d = self.as_dict(flat=True, sanitize=sanitize, d = self.as_dict(flat=True, sanitize=sanitize)
options=options)
return serializers.yaml(d) return serializers.yaml(d)
else: else:
raise ImportError("No YAML serializers available") raise ImportError("No YAML serializers available")
+28 -29
View File
@@ -725,13 +725,12 @@ class TestDALDictImportExport(unittest.TestCase):
assert isinstance(dbdict, dict) assert isinstance(dbdict, dict)
uri = dbdict["uri"] uri = dbdict["uri"]
assert isinstance(uri, basestring) and uri assert isinstance(uri, basestring) and uri
assert len(dbdict["items"]) == 2 assert len(dbdict["tables"]) == 2
assert len(dbdict["items"]["person"]["items"]) == 3 assert len(dbdict["tables"][0]["fields"]) == 3
assert dbdict["items"]["person"]["items"]["name"]["type"] == db.person.name.type assert dbdict["tables"][0]["fields"][1]["type"] == db.person.name.type
assert dbdict["items"]["person"]["items"]["name"]["default"] == db.person.name.default assert dbdict["tables"][0]["fields"][1]["default"] == db.person.name.default
assert dbdict
db2 = DAL(dbdict, check_reserved=['all']) db2 = DAL(**dbdict)
assert len(db.tables) == len(db2.tables) assert len(db.tables) == len(db2.tables)
assert hasattr(db2, "pet") and isinstance(db2.pet, Table) assert hasattr(db2, "pet") and isinstance(db2.pet, Table)
assert hasattr(db2.pet, "friend") and isinstance(db2.pet.friend, Field) assert hasattr(db2.pet, "friend") and isinstance(db2.pet.friend, Field)
@@ -749,7 +748,7 @@ class TestDALDictImportExport(unittest.TestCase):
unicode_keys = True unicode_keys = True
if sys.version < "2.6.5": if sys.version < "2.6.5":
unicode_keys = False unicode_keys = False
db3 = DAL(serializers.loads_json(dbjson, db3 = DAL(**serializers.loads_json(dbjson,
unicode_keys=unicode_keys)) unicode_keys=unicode_keys))
assert hasattr(db3, "person") and hasattr(db3.person, "uuid") and\ assert hasattr(db3, "person") and hasattr(db3.person, "uuid") and\
db3.person.uuid.type == db.person.uuid.type db3.person.uuid.type == db.person.uuid.type
@@ -760,18 +759,19 @@ class TestDALDictImportExport(unittest.TestCase):
mpfc = "Monty Python's Flying Circus" mpfc = "Monty Python's Flying Circus"
dbdict4 = {"uri": DEFAULT_URI, dbdict4 = {"uri": DEFAULT_URI,
"items":{"staff":{"items": {"name": "tables":[{"tablename": "tvshow",
{"default":"Michael"}, "fields": [{"fieldname": "name",
"food": "default":mpfc},
{"default":"Spam"}, {"fieldname": "rating",
"tvshow": "type":"double"}]},
{"type": "reference tvshow"} {"tablename": "staff",
}}, "fields": [{"fieldname": "name",
"tvshow":{"items": {"name": "default":"Michael"},
{"default":mpfc}, {"fieldname": "food",
"rating": "default":"Spam"},
{"type":"double"}}}}} {"fieldname": "tvshow",
db4 = DAL(dbdict4, check_reserved=['all']) "type": "reference tvshow"}]}]}
db4 = DAL(**dbdict4)
assert "staff" in db4.tables assert "staff" in db4.tables
assert "name" in db4.staff assert "name" in db4.staff
assert db4.tvshow.rating.type == "double" assert db4.tvshow.rating.type == "double"
@@ -785,20 +785,19 @@ class TestDALDictImportExport(unittest.TestCase):
db4.commit() db4.commit()
dbdict5 = {"uri": DEFAULT_URI} dbdict5 = {"uri": DEFAULT_URI}
db5 = DAL(dbdict5, check_reserved=['all']) db5 = DAL(**dbdict5)
assert db5.tables in ([], None) assert db5.tables in ([], None)
assert not (str(db5) in ("", None)) assert not (str(db5) in ("", None))
dbdict6 = {"uri": DEFAULT_URI, dbdict6 = {"uri": DEFAULT_URI,
"items":{"staff":{}, "tables":[{"tablename": "staff"},
"tvshow":{"items": {"name": {}, {"tablename": "tvshow",
"rating": "fields": [{"fieldname": "name"},
{"type":"double"} {"fieldname": "rating", "type":"double"}
} ]
} }]
} }
} db6 = DAL(**dbdict6)
db6 = DAL(dbdict6, check_reserved=['all'])
assert len(db6["staff"].fields) == 1 assert len(db6["staff"].fields) == 1
assert "name" in db6["tvshow"].fields assert "name" in db6["tvshow"].fields