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