Commit 60265302 authored by root's avatar root
Browse files

forget the past.

forget the past.
ChangeLog for yams
2006-04-10 -- 0.4.2
* fixed a bug when wildcard are used as subject and object of a
relation definition
* fixed default permissions for user entities
2006-03-31 -- 0.4.1
* finish api changes, ("final") refactoring
* cleanup
2006-03-29 -- 0.4.0
* major api changes
2006-03-14 -- 0.3.3
* // in schema definition files may be used to indicate a deprecated
* control which entities are considered by wildcarded relations refinition
using a variable on the relations file reader
2006-02-24 -- 0.3.2
* let subject_types and object_types rschema methods raise a KeyError if a
type is specified but not found for the relation (as it was documented)
2006-01-25 -- 0.3.1
* new function in schema2sql to give grants to a user
2005-07-29 -- 0.3.0
* support for a new 'inline' property on relation
* don't check for type differences for same relation names
2005-07-25 -- 0.2.0
* conditional drop into sql schema
* support for schema displaying using dot (graphviz)
2005-07-07 -- 0.1.2
* fixed bug with association types of symetric relation
2005-06-24 -- 0.1.1
* mini-modification for external use
2004-11-01 -- 0.1.0
* creation of changelog
include ChangeLog
include DEPENDS
include test/data/*.sql
include test/data/*.rel
include test/data/*.esql
include test/data/toignore
README for yams
The source tarball is available at
You may apt-get a debian package by adding ::
deb unstable/
to your /etc/apt/sources.list files. In bonus
From the source distribution, extract the tarball and run ::
python install
For debian and rpm packages, use your usual tools according to your Linux
Look in the doc/ subdirectory.
Comments, support, bug reports
Use the mailing list. Since we do not have publicly
available bug tracker yet, bug reports should be emailed there too.
You can subscribe to this mailing list at
Archives are available at
"""model object and utilities to define generic Entities/Relations schemas
:version: $Revision: 1.7 $
:organization: Logilab
:copyright: 2003-2006 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
:contact: --
__revision__ = "$Id:,v 1.7 2006-03-27 18:15:56 syt Exp $"
from yams._exceptions import *
from yams.schema import Schema, EntitySchema, RelationSchema
from yams.reader import SchemaLoader
# pylint: disable-msg=W0622
# Copyright (c) 2003-2006 LOGILAB S.A. (Paris, FRANCE).
# --
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation; either version 2 of the License, or (at your option) any later
# version.
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
"""yams packaging information
__revision__ = "$Id:,v 1.18 2006-04-10 15:29:18 syt Exp $"
# package name
modname = 'yams'
# release version
numversion = [0, 4, 2]
version = '.'.join([str(num) for num in numversion])
# license and copyright
license = 'GPL'
copyright = '''Copyright (c) 2004-2006 LOGILAB S.A. (Paris, FRANCE). --'''
# short and long description
short_desc = "entity / relation schema"
long_desc = """Yet Another Magic Schema !
A simple/generic but powerful entities / relations schema, suitable
to represent RDF like data. The schema is readable/writable from/to
various formats.
# author name and email
author = "Logilab"
author_email = ""
# home page
web = "" % modname
# mailing list
mailinglist = 'mailto://'
# download place
ftp = "" % modname
# is there some directories to include with the source installation
include_dirs = []
pyversions = ['2.2', '2.3']
"""Exceptions shared by different ER-Schema modules.
:organization: Logilab
:copyright: 2004 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
:contact: --
:version: $Revision: 1.4 $
__revision__ = "$Id:,v 1.4 2006-03-17 18:17:51 syt Exp $"
__docformat__ = "restructuredtext en"
#class MyException(
class SchemaError(Exception):
"""base class for schema exceptions"""
class InvalidEntity(SchemaError):
"""the entity is not valid according to its schema"""
msg = 'Invalid entity %s: \n%s'
## class InvalidAttributeValue(SchemaError):
## """an entity's attribute value is not valid according to its schema"""
class UnknownType(SchemaError):
"""using an unknown entity type"""
msg = 'Unknown type %s'
class BadSchemaDefinition(SchemaError):
"""error in the schema definition"""
msg = '%s line %s: %s'
args = ()
def __str__(self):
if len(self.args) == 3:
return self.msg % self.args
return ' '.join(self.args)
class ESQLParseError(Exception):
"""raised when a line is unparsable (end up by a warning)"""
msg = '%s: unable to parse %s'
args = ()
def __str__(self):
return self.msg % self.args
"""defines classes used to build a schema
__revision__ = "$Id:,v 1.6 2006-04-10 14:38:59 syt Exp $"
__docformat__ = "restructuredtext en"
__metaclass__ = type
from yams import BadSchemaDefinition
BASE_TYPES = ('String', 'Int', 'Float', 'Boolean', 'Date',
'Time', 'Datetime', 'Password', 'Bytes')
class Definition(object):
"""abstract class for entity / relation definition classes"""
meta = False
name = None
subject, object = None, None
def __init__(self, name=None, **kwargs):
self.__dict__.update(kwargs) = name or getattr(self, 'name', None) or self.__class__.__name__
# XXX check properties
#for key, val in kwargs.items():
# if not hasattr(self, key):
# self.error('no such property %r' % key)
def register_relations(self, schema):
raise NotImplementedError()
def add_relations(self, schema):
rschema = schema.rschema(
except KeyError:
# .rel file compat: the relation type may have not been added
rtype = RelationType(, symetric=self.symetric)
rschema = schema.add_relation_type(rtype)
for subj in self._actual_types(schema, self.subject):
for obj in self._actual_types(schema, self.object):
rdef = RelationDefinition(subj,, obj)
rdef.subject = subj
rdef.object = obj
def _actual_types(self, schema, etype):
if etype == '*':
return self._wildcard_etypes(schema)
elif etype == '**':
return self._pow_etypes(schema)
elif isinstance(etype, (tuple, list)):
return etype
return (etype,)
def _wildcard_etypes(self, schema):
for eschema in schema.entities(True):
if eschema.is_final() or eschema.meta:
yield eschema.type
def _pow_etypes(self, schema):
for eschema in schema.entities(True):
if eschema.is_final():
yield eschema.type
class EntityType(Definition):
relations = []
def __init__(self, *args, **kwargs):
super(EntityType, self).__init__(*args, **kwargs)
self.relations = self.relations[:]
def register_relations(self, schema):
order = 1
for relation in self.relations:
if isinstance(relation, SubjectRelation):
kwargs = relation.__dict__.copy()
del kwargs['name']
rdef = RelationDefinition(,,
object=relation.etype, order=order,
order += 1
elif isinstance(relation, ObjectRelation):
kwargs = relation.__dict__.copy()
del kwargs['name']
rdef = RelationDefinition(subject=relation.etype,,, order=order,
raise BadSchemaDefinition('duh?')
order += 1
class RelationBase(Definition):
cardinality = None
constraints = ()
symetric = False
def __init__(self, *args, **kwargs):
super(RelationBase, self).__init__(*args, **kwargs)
self.constraints = list(self.constraints)
if self.object is None:
cardinality = self.cardinality
if cardinality is None:
if self.object in BASE_TYPES: # XXX 1 instead of ? if not null constraint
self.cardinality = '?1'
self.cardinality = '**'
assert len(cardinality) == 2
assert cardinality[0] in '1?+*'
assert cardinality[1] in '1?+*'
class RelationType(RelationBase):
object = None
def register_relations(self, schema):
if getattr(self, 'subject', None) or getattr(self, 'object', None):
assert self.subject and self.object
class RelationDefinition(RelationBase):
subject = None
object = None
def __init__(self, subject, name, object, **kwargs):
self.subject = subject
self.object = object = name
super(RelationDefinition, self).__init__(**kwargs)
def register_relations(self, schema):
assert self.subject and self.object
class ObjectRelation(object):
cardinality = None
constraints = ()
def __init__(self, relations, rname, etype, **kwargs):
relations.append(self) = rname
self.etype = etype
self.constraints = list(self.constraints)
class SubjectRelation(ObjectRelation):
uid = False
indexed = False
fulltextindexed = False
internationalizable = False
default = None
class MetaEntityType(EntityType):
permissions = {
'read': ('managers', 'users', 'guests',),
'add': ('managers',),
'delete': ('managers',),
'update': ('managers', 'owners',),
meta = True
class UserEntityType(EntityType):
permissions = {
'read': ('managers', 'users', 'guests',),
'add': ('managers', 'users',),
'delete': ('managers', 'owners',),
'update': ('managers', 'owners',),
class MetaUserEntityType(UserEntityType):
meta = True
class MetaRelationType(RelationType):
permissions = {
'read': ('managers', 'users', 'guests',),
'add': ('managers',),
'delete': ('managers',),
meta = True
class UserRelationType(RelationType):
permissions = {
'read': ('managers', 'users', 'guests',),
'add': ('managers', 'users',),
'delete': ('managers', 'users',),
class MetaUserRelationType(UserRelationType):
meta = True
class AttributeRelationType(RelationType):
# just set permissions to None so default permissions are set
permissions = None
class MetaAttributeRelationType(AttributeRelationType):
meta = True
class FileReader(object):
"""abstract class for file readers"""
def __init__(self, loader, defaulthandler=None, readdeprecated=False):
self.loader = loader
self.default_hdlr = defaulthandler
self.read_deprecated = readdeprecated
self._current_file = None
self._current_line = None
self._current_lineno = None
def __call__(self, filepath):
self._current_file = filepath
def error(self, msg=None):
"""raise a contextual exception"""
raise BadSchemaDefinition(self._current_line, self._current_file, msg)
def read_file(self, filepath):
"""default implementation, calling .read_line method for each
non blank lines, and ignoring lines starting by '#' which are
considered as comment lines
for i, line in enumerate(file(filepath)):
line = line.strip()
if not line or line.startswith('#'):
self._current_line = line
self._current_lineno = i
if line.startswith('//'):
if self.read_deprecated:
def read_line(self, line):
"""need overriding !"""
raise NotImplementedError()
"""some default constraint classes
:version: $Revision: 1.1 $
:organization: Logilab
:copyright: 2003-2006 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
:contact: --
__revision__ = "$Id:,v 1.1 2006-03-30 19:50:56 syt Exp $"
__docformat__ = "restructuredtext en"
from yams.interfaces import IConstraint, IVocabularyConstraint
class BaseConstraint(object):
"""base class for constraints"""
__implements__ = IConstraint
def serialize(self):
"""called to make persistent valuable data of a constraint"""
return None
def deserialize(cls, value):
"""called to restore serialized data of a constraint. Should return
a `cls` instance
return cls()
deserialize = classmethod(deserialize)
# possible constraints ########################################################
class UniqueConstraint(BaseConstraint):
def check(self, entity, rtype, values):
"""return true if the value satisfy the constraint, else false"""
return True
class SizeConstraint(BaseConstraint):
"""the string size constraint :
if max is not None the string length must not be greater than max
if in is not None the string length must not be shorter than min
def __init__(self, max=None, min=None):
self.max = max
self.min = min
def check(self, entity, rtype, value):
"""return true if the value is in the interval specified by
self.min and self.max
if self.max is not None:
if len(value) > self.max:
return 0
if self.min is not None:
if len(value) < self.min:
return 0
return 1
def __str__(self):
res = 'size'
if self.max is not None:
res = '%s < %s' % (res, self.max)
if self.min is not None:
res = '%s < %s' % (self.min, res)
return res
def serialize(self):
"""simple text serialization"""
if self.max and self.min:
return 'min=%s,max=%s' % (self.min, self.max)
if self.max:
return 'max=%s' % (self.max)
return 'min=%s' % (self.min)
def deserialize(cls, value):
"""simple text deserialization"""
kwargs = {}
for adef in value.split(','):
key, val = [w.strip() for w in adef.split('=')]
assert key in ('min', 'max')
kwargs[str(key)] = int(val)
return cls(**kwargs)
deserialize = classmethod(deserialize)
class BoundConstraint(BaseConstraint):
"""the int/float bound constraint :
set a minimal or maximal value to a numerical value
__implements__ = IConstraint
def __init__(self, operator, bound=None):
assert operator in ('<=', '<', '>', '>=')
self.type = operator
self.bound = bound
def check(self, entity, rtype, value):
"""return true if the value satisfy the constraint, else false"""
return eval('%s %s %s' % (value, self.type, self.bound))
def __str__(self):
return 'value %s' % self.serialize()
def serialize(self):
"""simple text serialization"""
return '%s %s' % (self.type, self.bound)
def deserialize(cls, value):
"""simple text deserialization"""
operator = value.split()[0]
bound = ' '.join(value.split()[1:])
return cls(operator, bound)
deserialize = classmethod(deserialize)
class StaticVocabularyConstraint(BaseConstraint):
"""the static vocabulary constraint :
enforce the value to be in a predefined set vocabulary
__implements__ = IVocabularyConstraint
def __init__(self, values):
self.values = tuple(values)
def check(self, entity, rtype, value):
"""return true if the value is in the specific vocabulary"""
return value in self.vocabulary()
def vocabulary(self):
"""return a list of possible values for the attribute
return self.values
def __str__(self):
return 'value in (%s)' % self.serialize()
def serialize(self):
"""serialize possible values as a csv list of evaluable strings"""
return u', '.join([repr(unicode(word)) for word in self.vocabulary()])
def deserialize(cls, value):
"""deserialize possible values from a csv list of evaluable strings"""
return cls([eval(w) for w in value.split(', ')])
deserialize = classmethod(deserialize)
class MultipleStaticVocabularyConstraint(StaticVocabularyConstraint):
"""the multiple static vocabulary constraint :
enforce a list of values to be in a predefined set vocabulary
XXX never used
def check(self, entity, rtype, values):
"""return true if the values satisfy the constraint, else false"""
vocab = self.vocabulary()
for value in values: