10637.3.7
by Guilherme Salgado
merge devel |
1 |
#!/usr/bin/python -S
|
8687.15.9
by Karl Fogel
Add the copyright header block to more files (everything under database/). |
2 |
#
|
3 |
# Copyright 2009 Canonical Ltd. This software is licensed under the
|
|
4 |
# GNU Affero General Public License version 3 (see the file LICENSE).
|
|
1339
by Canonical.com Patch Queue Manager
Add generated database diagrams (initial cut) |
5 |
|
6 |
__metaclass__ = type |
|
7 |
||
8677.1.3
by Stuart Bishop
All scripts need to import _pythonpath to function correctly with buildout |
8 |
# pylint: disable-msg=W0403
|
9 |
import _pythonpath |
|
10 |
||
14612.2.9
by William Grant
Other bits and pieces. |
11 |
from ConfigParser import ( |
12 |
NoOptionError, |
|
13 |
SafeConfigParser, |
|
14 |
)
|
|
15 |
import os |
|
16 |
import re |
|
17 |
import sys |
|
18 |
||
19 |
import psycopg |
|
20 |
||
21 |
from security import ( |
|
22 |
CursorWrapper, |
|
23 |
DbSchema, |
|
24 |
)
|
|
25 |
||
1339
by Canonical.com Patch Queue Manager
Add generated database diagrams (initial cut) |
26 |
|
1806
by Canonical.com Patch Queue Manager
Always use sys.path.insert instead of sys.path.append to give precedence for launchpad libs that are inside our tree. r=stub |
27 |
sys.path.insert(0, os.path.join( |
1339
by Canonical.com Patch Queue Manager
Add generated database diagrams (initial cut) |
28 |
os.path.dirname(__file__), os.pardir, os.pardir, 'lib' |
29 |
))
|
|
30 |
||
31 |
config = SafeConfigParser() |
|
32 |
config.read(['diagram.cfg']) |
|
33 |
||
34 |
def trim(wanted_tables): |
|
35 |
'''Drop everything we don't want in the diagram'''
|
|
36 |
con = psycopg.connect('dbname=launchpad_dev') |
|
37 |
cur = CursorWrapper(con.cursor()) |
|
38 |
done = False |
|
39 |
||
40 |
# Drop everything we don't want to document
|
|
41 |
schema = DbSchema(con) |
|
42 |
all_objs = schema.values() |
|
43 |
while not done: |
|
44 |
schema = DbSchema(con) |
|
45 |
for obj in schema.values(): |
|
46 |
if obj.fullname in wanted_tables: |
|
47 |
continue
|
|
48 |
if obj.type == "view": |
|
49 |
print 'Dropping view %s' % obj.fullname |
|
50 |
cur.execute("DROP VIEW %s CASCADE" % obj.fullname) |
|
51 |
break
|
|
52 |
elif obj.type == "table": |
|
53 |
print 'Dropping table %s' % obj.fullname |
|
54 |
cur.execute("DROP TABLE %s CASCADE" % obj.fullname) |
|
55 |
break
|
|
56 |
if obj == all_objs[-1]: |
|
57 |
done = True |
|
58 |
con.commit() |
|
59 |
||
60 |
class Universe: |
|
61 |
def __contains__(self, i): |
|
62 |
'''The universe contains everything'''
|
|
63 |
return True |
|
64 |
||
10293.3.1
by Max Bowsher
Remove use of the deprecated sets module. |
65 |
all_tables = set() |
66 |
graphed_tables = set() |
|
1339
by Canonical.com Patch Queue Manager
Add generated database diagrams (initial cut) |
67 |
|
68 |
def tartup(filename, outfile, section): |
|
69 |
dot = open(filename).read() |
|
70 |
||
71 |
# Shorten timestamp declarations
|
|
72 |
dot = re.subn('timestamp without time zone', 'timestamp', dot)[0] |
|
73 |
||
74 |
# Collapse all whitespace that is safe
|
|
75 |
dot = re.subn('\s+', ' ', dot)[0] |
|
76 |
dot = re.subn(';', ';\n', dot)[0] |
|
77 |
||
78 |
lines = dot.split('\n') |
|
79 |
||
80 |
counter = 0 |
|
81 |
wanted_tables = [ |
|
82 |
s.strip() for s in config.get(section, 'tables').split(',') |
|
83 |
if s.strip() |
|
84 |
]
|
|
85 |
||
86 |
if '*' in wanted_tables: |
|
87 |
wanted_tables = Universe() |
|
88 |
else: |
|
89 |
exploded_wanted_tables = [] |
|
90 |
excluded_tables = [] |
|
91 |
for table in wanted_tables: |
|
92 |
if table.endswith('+'): |
|
93 |
if table.endswith('++'): |
|
94 |
table = table[:-2] |
|
95 |
exploded_wanted_tables.extend(explode(table, True)) |
|
96 |
else: |
|
97 |
table = table[:-1] |
|
98 |
exploded_wanted_tables.extend(explode(table, False)) |
|
99 |
if table.endswith('-'): |
|
100 |
table = table[:-1] |
|
101 |
excluded_tables.append(table) |
|
102 |
else: |
|
103 |
exploded_wanted_tables.append(table) |
|
104 |
wanted_tables = [ |
|
105 |
t for t in exploded_wanted_tables |
|
106 |
if t not in excluded_tables |
|
107 |
]
|
|
108 |
for t in wanted_tables: |
|
109 |
graphed_tables.add(t) |
|
110 |
||
111 |
for i in xrange(0, len(lines)): |
|
112 |
line = lines[i] |
|
8677.1.3
by Stuart Bishop
All scripts need to import _pythonpath to function correctly with buildout |
113 |
|
1339
by Canonical.com Patch Queue Manager
Add generated database diagrams (initial cut) |
114 |
# Trim tables we don't want to see
|
115 |
m = re.search(r'^\s* "(.+?)" \s+ \[shape', line, re.X) |
|
116 |
if m is not None: |
|
117 |
table = m.group(1) |
|
118 |
all_tables.add(table) |
|
119 |
if table not in wanted_tables: |
|
120 |
lines[i] = '' |
|
121 |
continue
|
|
122 |
||
123 |
# Trim foreign key relationships as specified, replacing with phantom
|
|
124 |
# links
|
|
125 |
m = re.search( |
|
1363
by Canonical.com Patch Queue Manager
Refactored VSourcePackageReleasePublishing for Keybuk |
126 |
r'^\s*"(.+?)" \s -> \s "(.*?)" \s ' |
127 |
r'\[label="(.*?)"\]; \s* $', |
|
1339
by Canonical.com Patch Queue Manager
Add generated database diagrams (initial cut) |
128 |
line, re.X |
129 |
)
|
|
130 |
if m is None: |
|
131 |
continue
|
|
132 |
||
133 |
counter += 1 |
|
134 |
||
135 |
t1 = m.group(1) |
|
136 |
t2 = m.group(2) |
|
137 |
||
138 |
# No links from an unwanted table to any other tables
|
|
139 |
if t1 not in wanted_tables: |
|
140 |
lines[i] = '' |
|
141 |
continue
|
|
8677.1.3
by Stuart Bishop
All scripts need to import _pythonpath to function correctly with buildout |
142 |
|
1339
by Canonical.com Patch Queue Manager
Add generated database diagrams (initial cut) |
143 |
# Links to ourself are fine, unless the table is not wanted
|
144 |
if t1 == t2: |
|
145 |
continue
|
|
8677.1.3
by Stuart Bishop
All scripts need to import _pythonpath to function correctly with buildout |
146 |
|
1339
by Canonical.com Patch Queue Manager
Add generated database diagrams (initial cut) |
147 |
# Get allowed links
|
148 |
allowed_link = True |
|
149 |
if t2 not in wanted_tables: |
|
150 |
allowed_link = False |
|
151 |
else: |
|
8677.1.3
by Stuart Bishop
All scripts need to import _pythonpath to function correctly with buildout |
152 |
for source, end in [(t1, t2), (t2, t1)]: |
1339
by Canonical.com Patch Queue Manager
Add generated database diagrams (initial cut) |
153 |
try: |
154 |
allowed = config.get(section, source) |
|
155 |
except NoOptionError: |
|
156 |
continue
|
|
157 |
allowed = [a.strip() for a in allowed.split(',') if a.strip()] |
|
158 |
if end not in allowed: |
|
159 |
allowed_link = False |
|
160 |
break
|
|
161 |
if allowed_link: |
|
162 |
continue
|
|
163 |
||
164 |
fake_node = 'fake_%s_%d' % (t2, counter) |
|
165 |
counter += 1 |
|
166 |
lines[i] = ''' |
|
167 |
"%(fake_node)s" [shape="ellipse",label="%(t2)s",color=red ]; |
|
1363
by Canonical.com Patch Queue Manager
Refactored VSourcePackageReleasePublishing for Keybuk |
168 |
"%(t1)s" -> "%(fake_node)s" [label="", len=.01, w=10000]; |
1339
by Canonical.com Patch Queue Manager
Add generated database diagrams (initial cut) |
169 |
''' % vars() |
170 |
open(outfile, 'w').write('\n'.join(lines)) |
|
171 |
||
172 |
def explode(table, two_way=False): |
|
173 |
con = psycopg.connect(config.get('DEFAULT', 'dbconnect')) |
|
174 |
cur = con.cursor() |
|
175 |
cur.execute(''' |
|
176 |
SELECT
|
|
177 |
src.relname AS src,
|
|
178 |
dst.relname AS dst
|
|
179 |
FROM
|
|
180 |
pg_constraint,
|
|
181 |
pg_class AS src,
|
|
182 |
pg_class AS dst
|
|
183 |
WHERE
|
|
184 |
(src.relname=%(table)s OR dst.relname=%(table)s) |
|
185 |
AND src.oid = conrelid
|
|
186 |
AND dst.oid = confrelid
|
|
187 |
''', vars()) |
|
188 |
references = list(cur.fetchall()) |
|
189 |
rv = [] |
|
190 |
for src, dst in references: |
|
191 |
if two_way: |
|
192 |
rv.append(src) |
|
193 |
rv.append(dst) |
|
194 |
elif src == table: |
|
195 |
rv.append(dst) |
|
196 |
return rv |
|
197 |
||
8677.1.3
by Stuart Bishop
All scripts need to import _pythonpath to function correctly with buildout |
198 |
|
4195.1.1
by Brad Crittenden
Implement upload and management of files associated with a product release. |
199 |
def main(filetypes): |
1339
by Canonical.com Patch Queue Manager
Add generated database diagrams (initial cut) |
200 |
|
4195.1.1
by Brad Crittenden
Implement upload and management of files associated with a product release. |
201 |
print filetypes |
1339
by Canonical.com Patch Queue Manager
Add generated database diagrams (initial cut) |
202 |
# Run postgresql_autodoc, creating autodoc.dot
|
203 |
cmd = ( |
|
204 |
"postgresql_autodoc -f autodoc -t dot -s public "
|
|
205 |
"-d launchpad_dev -l %s -u postgres" % os.pardir |
|
206 |
)
|
|
207 |
rv = os.system(cmd) |
|
208 |
assert rv == 0, 'Error %d running %r' % (rv, cmd) |
|
209 |
||
210 |
for section in config.sections(): |
|
211 |
if sys.argv[1:]: |
|
212 |
render = False |
|
213 |
for a in sys.argv[1:]: |
|
214 |
if section in a: |
|
215 |
render = True |
|
216 |
break
|
|
217 |
if not render: |
|
218 |
continue
|
|
219 |
# Munge the dot file because by default it is renders badly
|
|
220 |
print 'Tarting up autodoc.dot into +%s.dot' % section |
|
221 |
tartup('autodoc.dot', '+%s.dot' % section, section) |
|
222 |
||
223 |
# Render
|
|
4195.1.1
by Brad Crittenden
Implement upload and management of files associated with a product release. |
224 |
for lang in filetypes: |
225 |
print lang |
|
1363
by Canonical.com Patch Queue Manager
Refactored VSourcePackageReleasePublishing for Keybuk |
226 |
cmd = config.get(section, '%s_command' % lang) |
227 |
||
228 |
print ( |
|
229 |
'Producing %(section)s.%(lang)s from %(section)s.dot ' |
|
230 |
'using %(lang)s' % vars() |
|
231 |
)
|
|
232 |
||
233 |
csection = section.capitalize() |
|
234 |
||
235 |
cmd = ( |
|
236 |
'%(cmd)s -Glabel=%(csection)s -o %(section)s.%(lang)s ' |
|
237 |
'-T%(lang)s +%(section)s.dot' % vars() |
|
1339
by Canonical.com Patch Queue Manager
Add generated database diagrams (initial cut) |
238 |
)
|
1363
by Canonical.com Patch Queue Manager
Refactored VSourcePackageReleasePublishing for Keybuk |
239 |
print repr(cmd) |
240 |
rv = os.system(cmd) |
|
241 |
assert rv == 0, 'Error %d running %r' % (rv, cmd) |
|
1339
by Canonical.com Patch Queue Manager
Add generated database diagrams (initial cut) |
242 |
|
243 |
ungraphed_tables = [t for t in all_tables if t not in graphed_tables] |
|
244 |
if ungraphed_tables: |
|
245 |
print "The following tables are not on any diagrams except '*': ", |
|
246 |
print ', '.join(ungraphed_tables) |
|
247 |
||
248 |
if __name__ == '__main__': |
|
249 |
os.chdir('diagrams') |
|
4195.1.1
by Brad Crittenden
Implement upload and management of files associated with a product release. |
250 |
filetypes = ['svg', 'ps', 'png'] |
251 |
ft = [] |
|
252 |
while 1: |
|
253 |
try: |
|
254 |
if sys.argv[1] in filetypes: |
|
255 |
ft.append(sys.argv[1]) |
|
256 |
sys.argv = sys.argv[1:] |
|
257 |
else: |
|
258 |
break
|
|
259 |
except IndexError: |
|
260 |
break
|
|
261 |
||
262 |
if not ft: |
|
263 |
ft = filetypes |
|
264 |
||
265 |
main(ft) |
|
1339
by Canonical.com Patch Queue Manager
Add generated database diagrams (initial cut) |
266 |