[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