Commit 60265302 authored by root's avatar root
Browse files

forget the past.

forget the past.
parents
(^|/)\.svn($|/)
(^|/)\.hg($|/)
(^|/)\.hgtags($|/)
^log$
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
attribute
* 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
python-logilab-common
include ChangeLog
include DEPENDS
include test/data/*.sql
include test/data/*.rel
include test/data/*.esql
include test/data/toignore
README for yams
===============
Distributions
-------------
The source tarball is available at ftp://ftp.logilab.fr/pub/yams.
You may apt-get a debian package by adding ::
deb ftp://ftp.logilab.org/pub/debian unstable/
to your /etc/apt/sources.list files. In bonus
Install
-------
From the source distribution, extract the tarball and run ::
python setup.py install
For debian and rpm packages, use your usual tools according to your Linux
distribution.
Documentation
-------------
Look in the doc/ subdirectory.
Comments, support, bug reports
------------------------------
Use the python-projects@logilab.org 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
http://www.logilab.org/mailinglists/python_projects/mailinglist_register_form
Archives are available at
http://lists.logilab.org/pipermail/python-projects/
"""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: http://www.logilab.fr/ -- mailto:contact@logilab.fr
"""
__revision__ = "$Id: __init__.py,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).
# http://www.logilab.fr/ -- mailto:contact@logilab.fr
# 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: __pkginfo__.py,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).
http://www.logilab.fr/ -- mailto:contact@logilab.fr'''
# 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 = "devel@logilab.fr"
# home page
web = "http://www.logilab.org/projects/%s" % modname
# mailing list
mailinglist = 'mailto://python-projects@lists.logilab.org'
# download place
ftp = "ftp://ftp.logilab.org/pub/%s" % 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: http://www.logilab.fr/ -- mailto:contact@logilab.fr
:version: $Revision: 1.4 $
"""
__revision__ = "$Id: _exceptions.py,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: builder.py,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)
self.name = 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):
try:
rschema = schema.rschema(self.name)
except KeyError:
# .rel file compat: the relation type may have not been added
rtype = RelationType(name=self.name, 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, self.name, obj)
rdef.__dict__.update(self.__dict__)
rdef.subject = subj
rdef.object = obj
schema.add_relation_def(rdef)
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:
continue
yield eschema.type
def _pow_etypes(self, schema):
for eschema in schema.entities(True):
if eschema.is_final():
continue
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(subject=self.name, name=relation.name,
object=relation.etype, order=order,
**kwargs)
order += 1
elif isinstance(relation, ObjectRelation):
kwargs = relation.__dict__.copy()
del kwargs['name']
rdef = RelationDefinition(subject=relation.etype, name=relation.name,
object=self.name, order=order,
**kwargs)
else:
raise BadSchemaDefinition('duh?')
rdef.add_relations(schema)
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:
return
cardinality = self.cardinality
if cardinality is None:
if self.object in BASE_TYPES: # XXX 1 instead of ? if not null constraint
self.cardinality = '?1'
else:
self.cardinality = '**'
else:
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
self.add_relations(schema)
class RelationDefinition(RelationBase):
subject = None
object = None
def __init__(self, subject, name, object, **kwargs):
self.subject = subject
self.object = object
self.name = name
super(RelationDefinition, self).__init__(**kwargs)
def register_relations(self, schema):
assert self.subject and self.object
self.add_relations(schema)
class ObjectRelation(object):
cardinality = None
constraints = ()
def __init__(self, relations, rname, etype, **kwargs):
relations.append(self)
self.name = rname
self.etype = etype
self.constraints = list(self.constraints)
self.__dict__.update(kwargs)
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
self.read_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('#'):
continue
self._current_line = line
self._current_lineno = i
if line.startswith('//'):
if self.read_deprecated:
self.read_line(line[2:])
else:
self.read_line(line)
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: http://www.logilab.fr/ -- mailto:contact@logilab.fr
"""
__revision__ = "$Id: constraints.py,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()