Skip to content

Commit 143b03a

Browse files
author
Andrey Golovizin
committed
Initial commit.
0 parents commit 143b03a

File tree

9 files changed

+276
-0
lines changed

9 files changed

+276
-0
lines changed

.hgignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
syntax: glob
2+
*.pyc
3+
*.egg-info
4+
*~

README

Whitespace-only changes.

django_dumpdb/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
"""Django dumpdb/restoredb management commands."""
2+
3+
VERSION = (0, 1)
4+
5+
__version__ = '.'.join(unicode(part) for part in VERSION)

django_dumpdb/dumprestore.py

Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
import sys
2+
import re
3+
from itertools import chain
4+
5+
from json import loads
6+
7+
from django.db import connection, transaction
8+
from django.db.models import get_apps, get_models
9+
from django.conf import settings
10+
from django.core.serializers.json import DjangoJSONEncoder
11+
from django.core.management import color
12+
13+
HEADER = '# Django database dump'
14+
HEADER_RE = re.compile('#\sDjango database dump')
15+
16+
qn = connection.ops.quote_name
17+
dumps = DjangoJSONEncoder(ensure_ascii=False).encode
18+
19+
class DumpRestoreError(Exception):
20+
pass
21+
22+
23+
class RestoreError(DumpRestoreError):
24+
pass
25+
26+
27+
DISABLE_FOREIGN_KEYS_SQL = {
28+
'sqlite3': None,
29+
'mysql': 'SET foreign_key_checks = 0',
30+
'postgresql_psycopg2': 'SET CONSTRAINTS ALL DEFERRED',
31+
}
32+
33+
34+
class EndOfDump(Exception):
35+
pass
36+
37+
38+
class CleverIterator(object):
39+
def __init__(self, seq):
40+
self.iterator = iter(seq)
41+
try:
42+
self.first = next(self.iterator)
43+
self.empty = False
44+
except StopIteration:
45+
self.empty = True
46+
47+
def __getitem__(self, index):
48+
if self.empty:
49+
raise IndexError
50+
if index != 0:
51+
raise NotImplementedError
52+
return self.first
53+
54+
def __nonzero__(self):
55+
return not self.empty
56+
57+
def __iter__(self):
58+
if self.empty:
59+
raise StopIteration
60+
yield self.first
61+
for item in self.iterator:
62+
yield item
63+
self.empty = True
64+
65+
def next(self):
66+
return iter(self).next()
67+
68+
69+
def get_all_models():
70+
""" Get all models, grouped by apps. """
71+
for app in get_apps():
72+
for model in get_models(app, include_auto_created=True):
73+
yield model
74+
75+
76+
def server_side_cursor(connection):
77+
if not connection.connection:
78+
connection.cursor() # initialize DB connection
79+
80+
backend = settings.DATABASE_ENGINE
81+
if backend == 'postgresql_psycopg2':
82+
return connection.connection.cursor(name='dump')
83+
elif backend == 'mysql':
84+
from MySQLdb.cursors import SSCursor
85+
return connection.connection.cursor(SSCursor)
86+
else:
87+
return connection.cursor()
88+
89+
90+
def dump_table(table, fields, pk, converters):
91+
cursor = server_side_cursor(connection)
92+
qn = connection.ops.quote_name
93+
fields_sql = ', '.join(qn(field) for field in fields)
94+
table_sql = qn(table)
95+
pk_sql = qn(pk)
96+
yield '# %s' % dumps((table, fields))
97+
cursor.execute('SELECT %s FROM %s ORDER BY %s' % (fields_sql, table_sql, pk_sql))
98+
for row in cursor:
99+
yield dumps([converter(value) for converter, value in zip(converters, row)])
100+
yield ''
101+
cursor.close()
102+
103+
104+
def dump_model(model):
105+
table = model._meta.db_table
106+
fields = [field.column for field in model._meta.local_fields]
107+
converters = [field.get_db_prep_value for field in model._meta.local_fields]
108+
pk = model._meta.pk.column
109+
return dump_table(table, fields, pk, converters)
110+
111+
112+
def dump_all():
113+
return chain(*(dump_model(model) for model in get_all_models()))
114+
115+
116+
def dump(file=sys.stdout):
117+
file.write(HEADER + '\n\n')
118+
for line in dump_all():
119+
file.write(line.encode('UTF-8') + '\n')
120+
121+
122+
# http://code.djangoproject.com/ticket/9964
123+
#@transaction.commit_on_success
124+
def load(file=sys.stdin):
125+
""" Load data from file into the DB. """
126+
try:
127+
transaction.enter_transaction_management()
128+
transaction.managed(True)
129+
130+
disable_foreign_keys()
131+
for table, fields, rows in parse_file(file):
132+
load_table(table, fields, rows)
133+
134+
reset_sequences()
135+
except:
136+
transaction.rollback()
137+
raise
138+
else:
139+
transaction.commit()
140+
finally:
141+
transaction.leave_transaction_management()
142+
143+
144+
def parse_file(lines):
145+
""" Return a sequence of (table_name, fields, data_rows) for each dumped table. """
146+
find_main_header(lines)
147+
148+
while True:
149+
header = find_table_header(lines)
150+
if not header: #EOF
151+
break
152+
table, fields = header
153+
yield table, fields, read_table_rows(lines)
154+
155+
156+
def find_main_header(lines):
157+
for line in lines:
158+
match = HEADER_RE.search(line)
159+
if match:
160+
return
161+
raise RestoreError('File header not found - not a valid database dump.')
162+
163+
164+
def find_table_header(lines):
165+
""" Find table data header. """
166+
for line in lines:
167+
if line.startswith('#'):
168+
header = line.lstrip('#')
169+
table, fields = loads(header)
170+
return table, fields
171+
172+
173+
def read_table_rows(lines):
174+
""" Read table rows, stop at EOF or a blank line. """
175+
for line in lines:
176+
if not line or line.isspace():
177+
break
178+
else:
179+
yield loads(line)
180+
181+
182+
def load_table(table, fields, rows):
183+
""" Load data into the given table with the given fields. """
184+
truncate_table(table)
185+
table_sql = qn(table)
186+
fields_sql = ', '.join(qn(field) for field in fields)
187+
params_sql = ', '.join('%s' for field in fields)
188+
insert_sql = 'INSERT INTO %s (%s) VALUES (%s)' % (table_sql, fields_sql, params_sql)
189+
executemany(insert_sql, rows)
190+
191+
192+
def executemany(sql, rows):
193+
cursor = connection.cursor()
194+
for row in rows:
195+
cursor.execute(sql, row)
196+
197+
198+
def truncate_table(table):
199+
""" Delete all rows from the given table. """
200+
cursor = connection.cursor()
201+
cursor.execute('DELETE FROM %s' % qn(table))
202+
203+
204+
def disable_foreign_keys():
205+
""" Disable foreign key constraint checks using DB-specific SQL. """
206+
sql = DISABLE_FOREIGN_KEYS_SQL[settings.DATABASE_ENGINE]
207+
if sql:
208+
cursor = connection.cursor()
209+
cursor.execute(sql)
210+
211+
212+
def reset_sequences():
213+
""" Reset DB sequences, if needed. """
214+
models = get_all_models()
215+
sequence_reset_sql = connection.ops.sequence_reset_sql(color.no_style(), models)
216+
if sequence_reset_sql:
217+
cursor = connection.cursor()
218+
for line in sequence_reset_sql:
219+
cursor.execute(line)

django_dumpdb/management/__init__.py

Whitespace-only changes.

django_dumpdb/management/commands/__init__.py

Whitespace-only changes.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from django.core.management.base import NoArgsCommand
2+
3+
4+
class Command(NoArgsCommand):
5+
def handle_noargs(self, **options):
6+
from django_dumpdb.dumprestore import dump
7+
dump()
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from django.core.management.base import NoArgsCommand
2+
3+
4+
class Command(NoArgsCommand):
5+
def handle_noargs(self, **options):
6+
from django_dumpdb.dumprestore import load
7+
load()

setup.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
#!/usr/bin/env python
2+
3+
try:
4+
from setuptools import setup
5+
except ImportError:
6+
from distutils.core import setup
7+
8+
import django_dumpdb
9+
10+
11+
def get_long_description():
12+
return open('README').read()
13+
14+
setup(
15+
name='django-dumpdb',
16+
version=django_dumpdb.__version__,
17+
description='Django dumpdb/restoredb management commands',
18+
long_description=get_long_description(),
19+
author='Andrey Golovizin',
20+
author_email='golovizin@gmail.com',
21+
url='http://code.google.com/p/django-dumpdb/',
22+
packages=['django_dumpdb'],
23+
license='MIT',
24+
classifiers=[
25+
'Development Status :: 4 - Beta',
26+
'Operating System :: OS Independent',
27+
'License :: OSI Approved :: MIT License',
28+
'Intended Audience :: Developers',
29+
'Programming Language :: Python',
30+
'Environment :: Web Environment',
31+
'Framework :: Django',
32+
'Topic :: Utilities',
33+
],
34+
)

0 commit comments

Comments
 (0)