Nicer error reporting if interface is missing.
[zeroinstall.git] / model.py
blob886fdeba98ffc84d648ff8b34bd8cdbff6ecf6a4
1 """In-memory representation of the dependency graph."""
3 import os
5 network_offline = 'off-line'
6 network_minimal = 'minimal'
7 network_full = 'full'
8 network_levels = (network_offline, network_minimal, network_full)
10 stability_levels = {} # Name -> Stability
12 class Stability(object):
13 __slots__ = ['level', 'name', 'description']
14 def __init__(self, level, name, description):
15 self.level = level
16 self.name = name
17 self.description = description
18 assert name not in stability_levels
19 stability_levels[name] = self
21 def __cmp__(self, other):
22 return cmp(self.level, other.level)
24 def __str__(self):
25 return self.name
27 insecure = Stability(0, 'insecure', 'This is a security risk')
28 buggy = Stability(5, 'buggy', 'Known to have serious bugs')
29 developer = Stability(10, 'developer', 'Work-in-progress - bugs likely')
30 testing = Stability(20, 'testing', 'Stability unknown - please test!')
31 stable = Stability(30, 'stable', 'Tested - no serious problems found')
32 preferred = Stability(40, 'preferred', 'Best of all - must be set manually')
34 class Restriction(object):
35 """A Restriction limits the allowed implementations of an Interface."""
37 class Binding(object):
38 """Information about how the choice of a Dependency is made known
39 to the application being run."""
41 class EnvironmentBinding(Binding):
42 __slots__ = ['name', 'insert']
44 def __init__(self, name, insert):
45 self.name = name
46 self.insert = insert
48 def __str__(self):
49 return "<environ %s += %s>" % (self.name, self.insert)
51 class Dependency(object):
52 """A Dependency indicates that an Implementation requires some additional
53 code to function, specified by another Interface."""
54 __slots__ = ['interface', 'restrictions', 'bindings']
56 def __init__(self, interface):
57 assert isinstance(interface, (str, unicode))
58 self.interface = interface
59 self.restrictions = []
60 self.bindings = []
62 def get_interface(self):
63 return get_interface(self.interface)
65 def __str__(self):
66 return "<Dependency on %s; bindings: %d>" % (self.interface, len(self.bindings))
68 class Implementation(object):
69 """An Implementation is a package which implements an Interface."""
70 __slots__ = ['path', 'arch', 'upstream_stability', 'user_stability',
71 'version', 'size', 'dependencies']
73 def __init__(self, path):
74 assert path
75 self.path = path
76 self.size = None
77 self.version = None
78 self.user_stability = None
79 self.upstream_stability = None
80 self.arch = None
81 self.dependencies = {} # URI -> Dependency
83 def get_stability(self):
84 return self.user_stability or self.upstream_stability or testing
86 def get_cached(self):
87 return os.path.exists(self.path)
89 def get_version(self):
90 return '.'.join(map(str, self.version))
92 def __str__(self):
93 return self.path
95 def __cmp__(self, other):
96 return cmp(other.version, self.version)
98 class Interface(object):
99 """An Interface represents some contract of behaviour."""
100 __slots__ = ['uri', 'implementations', 'name', 'uptodate', 'description', 'summary',
101 'stability_policy', 'last_updated']
103 # stability_policy:
104 # Implementations at this level or higher are preferred.
105 # Lower levels are used only if there is no other choice.
107 # last_updated:
108 # The time we last updated the cached information (the time is stored
109 # in the cache). None means we haven't even read the cache yet.
111 def __init__(self, uri):
112 assert uri
113 self.uri = uri
114 self.implementations = {} # Path -> Implementation
115 self.name = None
116 self.last_updated = None
117 self.uptodate = False
118 self.summary = None
119 self.description = None
120 self.stability_policy = None
122 def get_name(self):
123 return self.name or '(' + os.path.basename(self.uri) + ')'
125 def __repr__(self):
126 return "<Interface %s>" % self.uri
128 def get_impl(self, path):
129 if path not in self.implementations:
130 self.implementations[path] = Implementation(path)
131 return self.implementations[path]
133 def set_stability_policy(self, new):
134 assert new is None or isinstance(new, Stability)
135 self.stability_policy = new
137 def changed(self):
138 for w in self.watchers(): w(self)
140 _interfaces = {} # URI -> Interface
142 def get_interface(uri):
143 if type(uri) == str:
144 uri = unicode(uri)
145 assert isinstance(uri, unicode)
146 if uri not in _interfaces:
147 _interfaces[uri] = Interface(uri)
148 return _interfaces[uri]
150 def escape(uri):
151 "Convert each space to %20, etc"
152 import re
153 return re.sub('[^-_.a-zA-Z0-9]',
154 lambda match: '%%%02x' % ord(match.group(0)),
155 uri.encode('utf-8'))
157 class SafeException(Exception):
158 pass