[python-debian/master] simplify relations API using some sane defaults
Stefano Zacchiroli
zack at upsilon.cc
Thu Jun 19 15:53:08 UTC 2008
in particular:
- package realtionships are now returned as a single dictionary using
the relations() method, no longer polluting the Deb822 method space
- the returned dictionary always contain all possible relationship
names, which are empty if they were not present in the Deb822 instance
- version/arch keys are always present in the final relationship parts,
though they are None if the relationship was not versione (or,
respectively, architecture-specific)
---
TODO | 5 --
debian_bundle/deb822.py | 148 ++++++++++++++++++++++++++-------------------
examples/deb822/depgraph | 20 +++---
3 files changed, 95 insertions(+), 78 deletions(-)
diff --git a/TODO b/TODO
index 0d9ee79..af2e1b9 100644
--- a/TODO
+++ b/TODO
@@ -5,8 +5,3 @@
- add tests
- change key name to access package name from "name" to "package", that way
it is more consistent with legacy package names
- - instead of adding top-level methods, add a single top-level property
- returning a dictionary of all possible package relationships
- - put in the above dictionary all the possible dependencies, no matter if
- they appear or not in the actual package (at worst they will be empty
- lists), this will simplify using deb822
diff --git a/debian_bundle/deb822.py b/debian_bundle/deb822.py
index e1f1277..51f1e14 100644
--- a/debian_bundle/deb822.py
+++ b/debian_bundle/deb822.py
@@ -393,21 +393,14 @@ class Deb822(Deb822Dict):
###
-class PkgRel(object):
- """Package relationships
+class PkgRelation(object):
+ """Inter-package relationships
Structured representation of the relationships of a package to another,
i.e. of what can appear in a Deb882 field like Depends, Recommends,
Suggests, ... (see Debian Policy 7.1).
"""
- def __init__(self, rels=[]):
- """Argument is a list of dependencies logically AND-ed together.
-
- In Deb822 syntax that would be the top-level comma-separated list.
- """
- self.__rels = rels
-
# XXX *NOT* a real dependency parser, and that is not even a goal here, we
# just parse as much as we need to split the various parts composing a
# dependency, checking their correctness wrt policy is out of scope
@@ -418,11 +411,9 @@ class PkgRel(object):
__blank_sep_RE = re.compile(r'\s*')
@classmethod
- def parse_rels(cls, raw):
+ def parse_relations(cls, raw):
"""Parse a package relationship string (i.e. the value of a field like
Depends, Recommends, Build-Depends ...)
-
- For a description of return value format see the rels property
"""
def parse_archs(raw):
# assumption: no space beween '!' and architecture name
@@ -441,27 +432,83 @@ class PkgRel(object):
d = { 'name': parts['name'] }
if not (parts['relop'] is None or parts['version'] is None):
d['version'] = (parts['relop'], parts['version'])
- if not (parts['archs'] is None):
+ else:
+ d['version'] = None
+ if parts['archs'] is None:
+ d['version'] = None
+ else:
d['arch'] = parse_archs(parts['archs'])
return d
else:
print >> sys.stderr, \
'deb822.py: WARNING: cannot parse package' \
' relationship "%s", returning it raw' % raw
- return { 'name': raw }
+ return { 'name': raw, 'version': None, 'arch': None }
tl_deps = cls.__comma_sep_RE.split(raw.strip()) # top-level deps
cnf = map(cls.__pipe_sep_RE.split, tl_deps)
return map(lambda or_deps: map(parse_rel, or_deps), cnf)
+
+class _lowercase_dict(dict):
+ """Dictionary wrapper which lowercase keys upon lookup."""
+
+ def __getitem__(self, key):
+ return dict.__getitem__(self, key.lower())
+
+
+class _PkgRelationMixin(object):
+ """Package relationship mixin
+
+ Inheriting from this mixin you can extend a Deb882 object with attributes
+ letting you access inter-package relationship in a structured way, rather
+ than as strings. For example, while you can usually use pkg['depends'] to
+ obtain the Depends string of package pkg, mixing in with this class you
+ gain pkg.depends to access Depends as a Pkgrel instance
+
+ To use, subclass _PkgRelationMixin from a class with a _relationship_fields
+ attribute. It should be a list of field names for which structured access
+ is desired; for each of them a method wild be added to the inherited class.
+ The method name will be the lowercase version of field name; '-' will be
+ mangled as '_'. The method would return relationships in the same format of
+ the PkgRelation' relations property.
+
+ See Packages and Sources as examples.
+ """
+
+ def __init__(self, *args, **kwargs):
+ self.__relations = _lowercase_dict({})
+ self.__parsed_relations = False
+ for name in self._relationship_fields:
+ # To avoid reimplementing Deb822 key lookup logic we use a really
+ # simple dict subclass which just lowercase keys upon lookup. Since
+ # dictionary building happens only here, we ensure that all keys
+ # are in fact lowercase.
+ # With this trick we enable users to use the same key (i.e. field
+ # name) of Deb822 objects on the dictionary returned by the
+ # relations property.
+ keyname = name.lower()
+ if self.has_key(name):
+ self.__relations[keyname] = None # lazy value
+ # all lazy values will be expanded before setting
+ # __parsed_relations to True
+ else:
+ self.__relations[keyname] = []
+
@property
- def rels(self):
- """Get raw package relationships
+ def relations(self):
+ """Return a dictionary of inter-package relationships among the current
+ and other packages.
- Relationships are returned as lists of lists of dictionaries (see below
- for some examples).
+ Dictionary keys depend on the package kind. Binary packages have keys
+ like 'depends', 'recommends', ... while source packages have keys like
+ 'build-depends', 'build-depends-indep' and so on. See the Debian policy
+ for the comprehensive field list.
- The semantics is as follows:
+ Dictionary values are package relationships returned as lists of lists
+ of dictionaries (see below for some examples).
+
+ The encoding of package relationships is as follows:
- the top-level lists corresponds to the comma-separated list of
Deb822, their components form a conjuction, i.e. they have to be
AND-ed together
@@ -470,17 +517,15 @@ class PkgRel(object):
together
- member of the inner lists are dictionaries with the following keys:
- name: package (or virtual package) name
- - version: a pair <operator, version>. operator is one of "<<",
+ - version: A pair <operator, version> if the relationship is
+ versioned, None otherwise. operator is one of "<<",
"<=", "=", ">=", ">>"; version is the given version as
a string.
- This key is present only if the relationship is
- versioned
- - arch: a list of pairs <polarity, architecture>; polarity is a
- boolean (false if the architecture is negated with "!",
- true otherwise), architecture the Debian archtiecture
- name as a string.
- This key is present only if the relationship is
- architecture specific
+ - arch: A list of pairs <polarity, architecture> if the
+ relationship is architecture specific, None otherwise.
+ Polarity is a boolean (false if the architecture is
+ negated with "!", true otherwise), architecture the
+ Debian archtiecture name as a string.
Examples:
@@ -493,45 +538,22 @@ class PkgRel(object):
[ [ {'name': 'tcl8.4-dev'} ],
[ {'name': 'procps', 'arch': (false, 'hurd-i386')} ] ]
"""
- return self.__rels
-
-class _PkgRelMixin(object):
- """Package relationship mixin
-
- Inheriting from this mixin you can extend a Deb882 object with attributes
- letting you access inter-package relationship in a structured way, rather
- than as strings. For example, while you can usually use pkg['depends'] to
- obtain the Depends string of package pkg, mixing in with this class you
- gain pkg.depends to access Depends as a Pkgrel instance
-
- To use, subclass _PkgRelMixin from a class with a _relationship_fields
- attribute. It should be a list of field names for which structured access
- is desired; for each of them a method wild be added to the inherited class.
- The method name will be the lowercase version of field name; '-' will be
- mangled as '_'. The method would return relationships in the same format of
- the PkgRel' rels property.
-
- See Packages and Sources as examples.
- """
-
- def __init__(self, *args, **kwargs):
- for name in self._relationship_fields:
- if self.has_key(name):
- methodname = name.lower().replace('-', '_')
- method = new.instancemethod(
- lambda self, name=name: PkgRel.parse_rels(self[name]),
- self, _PkgRelMixin)
- setattr(_PkgRelMixin, methodname, method)
+ if not self.__parsed_relations:
+ lazy_rels = filter(lambda n: self.__relations[n] is None,
+ self.__relations.keys())
+ for n in lazy_rels:
+ self.__relations[n] = PkgRelation.parse_relations(self[n])
+ self.__parsed_relations = True
+ return self.__relations
class _multivalued(Deb822):
"""A class with (R/W) support for multivalued fields.
-
+
To use, create a subclass with a _multivalued_fields attribute. It should
be a dictionary with *lower-case* keys, with lists of human-readable
identifiers of the fields as the values. Please see Dsc, Changes, and
PdiffIndex as examples.
"""
-
def __init__(self, *args, **kwargs):
Deb822.__init__(self, *args, **kwargs)
@@ -707,7 +729,7 @@ class Release(_multivalued):
return max(lengths)
-class Sources(Dsc, _PkgRelMixin):
+class Sources(Dsc, _PkgRelationMixin):
"""Represent an APT source package list"""
_relationship_fields = [ 'build-depends', 'build-depends-indep',
@@ -715,10 +737,10 @@ class Sources(Dsc, _PkgRelMixin):
def __init__(self, *args, **kwargs):
Dsc.__init__(self, *args, **kwargs)
- _PkgRelMixin.__init__(self, *args, **kwargs)
+ _PkgRelationMixin.__init__(self, *args, **kwargs)
-class Packages(Deb822, _PkgRelMixin):
+class Packages(Deb822, _PkgRelationMixin):
"""Represent an APT binary package list"""
_relationship_fields = [ 'depends', 'pre-depends', 'recommends',
@@ -727,7 +749,7 @@ class Packages(Deb822, _PkgRelMixin):
def __init__(self, *args, **kwargs):
Deb822.__init__(self, *args, **kwargs)
- _PkgRelMixin.__init__(self, *args, **kwargs)
+ _PkgRelationMixin.__init__(self, *args, **kwargs)
###
diff --git a/examples/deb822/depgraph b/examples/deb822/depgraph
index 7697e74..7423a48 100755
--- a/examples/deb822/depgraph
+++ b/examples/deb822/depgraph
@@ -39,16 +39,16 @@ def main():
print "digraph depgraph {"
for pkg in deb822.Packages.iter_paragraphs(file(sys.argv[1])):
name = pkg['package']
- if pkg.has_key('depends'):
- for deps in pkg.depends():
- if len(deps) == 1:
- emit_arc(name, deps[0]['name'])
- else: # output an OR node
- or_node = get_id()
- emit_arc(name, or_node)
- emit_node(or_node, 'OR')
- for dep in deps:
- emit_arc(or_node, dep['name'])
+ rels = pkg.relations
+ for deps in rels['depends']:
+ if len(deps) == 1:
+ emit_arc(name, deps[0]['name'])
+ else: # output an OR node
+ or_node = get_id()
+ emit_arc(name, or_node)
+ emit_node(or_node, 'OR')
+ for dep in deps:
+ emit_arc(or_node, dep['name'])
print "}"
if __name__ == '__main__':
--
1.5.4.2
More information about the pkg-python-debian-commits
mailing list