Skip to content

Commit 861976f

Browse files
authored
Merge pull request #23 from andersinno/add-extra-params-support
Add support for dump CLI extra parameters
2 parents df9a399 + c9fb408 commit 861976f

File tree

6 files changed

+191
-3
lines changed

6 files changed

+191
-3
lines changed

README.md

+10
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,11 @@ config:
7777
addons:
7878
- some.other.package
7979
- yet.another.package
80+
extra_parameters: # These parameters will be passed to the dump tool CLI
81+
mysqldump:
82+
- "--single-transaction" # Included by default
83+
pg_dump:
84+
- "--exclude-table=something"
8085
strategy:
8186
user:
8287
first_name: name.first_name
@@ -90,6 +95,11 @@ be looking for sanitizer functions. They are completely optional and can
9095
be omitted, in which case only sanitizer functions defined in package
9196
called `sanitizers` and built-in sanitizers will be used instead.
9297

98+
It's also possible to define extra parameters to pass to the dump tool (
99+
`mysqldump` or `pg_dump`). By default, `mysqldump` will include the
100+
`--single-transaction` extra parameter. You can disable this by defining the
101+
extra parameters in the config file explicitly, e.g. with an empty array `[]`.
102+
93103
The `strategy` portion of the configuration contains the actual
94104
sanitation rules. First you define name of the database table (in the
95105
example that would be `user`) followed by column names in that table

database_sanitizer/config.py

+50
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99

1010
__all__ = ("Configuration", "ConfigurationError")
1111

12+
MYSQLDUMP_DEFAULT_PARAMETERS = ["--single-transaction"]
13+
PG_DUMP_DEFAULT_PARAMETERS = []
14+
1215

1316
class ConfigurationError(ValueError):
1417
"""
@@ -24,6 +27,8 @@ class Configuration(object):
2427
def __init__(self):
2528
self.sanitizers = {}
2629
self.addon_packages = []
30+
self.mysqldump_params = []
31+
self.pg_dump_params = []
2732

2833
@classmethod
2934
def from_file(cls, filename):
@@ -64,6 +69,51 @@ def load(self, config_data):
6469

6570
self.load_addon_packages(config_data)
6671
self.load_sanitizers(config_data)
72+
self.load_dump_extra_parameters(config_data)
73+
74+
def load_dump_extra_parameters(self, config_data):
75+
"""
76+
Loads extra parameters for mysqldump and/or pg_dump CLI usage. These
77+
parameters should be added to the mysqldump and/or pg_dump command call
78+
when taking a dump.
79+
80+
:param config_data: Already parsed configuration data, as dictionary.
81+
:type config_data: dict[str,any]
82+
"""
83+
section_config = config_data.get("config", {})
84+
if not isinstance(section_config, dict):
85+
raise ConfigurationError(
86+
"'config' is %s instead of dict" % (
87+
type(section_config),
88+
),
89+
)
90+
91+
section_extra_parameters = section_config.get("extra_parameters", {})
92+
if not isinstance(section_extra_parameters, dict):
93+
raise ConfigurationError(
94+
"'config.extra_parameters' is %s instead of dict" % (
95+
type(section_extra_parameters),
96+
),
97+
)
98+
99+
mysqldump_params = section_extra_parameters.get("mysqldump", MYSQLDUMP_DEFAULT_PARAMETERS)
100+
if not isinstance(mysqldump_params, list):
101+
raise ConfigurationError(
102+
"'config.extra_parameters.mysqldump' is %s instead of list" % (
103+
type(mysqldump_params),
104+
),
105+
)
106+
107+
pg_dump_params = section_extra_parameters.get("pg_dump", PG_DUMP_DEFAULT_PARAMETERS)
108+
if not isinstance(pg_dump_params, list):
109+
raise ConfigurationError(
110+
"'config.extra_parameters.pg_dump' is %s instead of list" % (
111+
type(pg_dump_params),
112+
),
113+
)
114+
115+
self.mysqldump_params = mysqldump_params
116+
self.pg_dump_params = pg_dump_params
67117

68118
def load_addon_packages(self, config_data):
69119
"""

database_sanitizer/dump/mysql.py

+6-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
encode_mysql_literal,
1212
get_mysqldump_args_and_env_from_url,
1313
)
14+
from ..config import MYSQLDUMP_DEFAULT_PARAMETERS
1415

1516
#: Regular expression which matches `INSERT INTO` statements produced by the
1617
#: `mysqldump` utility, even when extended inserts have been enabled.
@@ -57,8 +58,12 @@ def sanitize(url, config):
5758

5859
args, env = get_mysqldump_args_and_env_from_url(url=url)
5960

61+
extra_params = MYSQLDUMP_DEFAULT_PARAMETERS
62+
if config:
63+
extra_params = config.mysqldump_params
64+
6065
process = subprocess.Popen(
61-
args=["mysqldump"] + args,
66+
args=["mysqldump"] + args + extra_params,
6267
env=env,
6368
stdout=subprocess.PIPE,
6469
)

database_sanitizer/dump/postgres.py

+6-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import subprocess
88

99
from ..utils.postgres import decode_copy_value, encode_copy_value
10+
from ..config import PG_DUMP_DEFAULT_PARAMETERS
1011

1112
COPY_LINE_PATTERN = re.compile(
1213
r"^COPY \"(?P<schema>[^\"]*)\".\"(?P<table>[^\"]*)\" "
@@ -31,6 +32,10 @@ def sanitize(url, config):
3132
if url.scheme not in ("postgres", "postgresql", "postgis"):
3233
raise ValueError("Unsupported database type: '%s'" % (url.scheme,))
3334

35+
extra_params = PG_DUMP_DEFAULT_PARAMETERS
36+
if config:
37+
extra_params = config.pg_dump_params
38+
3439
process = subprocess.Popen(
3540
(
3641
"pg_dump",
@@ -42,7 +47,7 @@ def sanitize(url, config):
4247
# URL as argument to the command.
4348
"--dbname",
4449
url.geturl().replace('postgis://', 'postgresql://'),
45-
),
50+
) + tuple(extra_params),
4651
stdout=subprocess.PIPE,
4752
)
4853

database_sanitizer/tests/test_config.py

+48
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,54 @@ def test_load_config_data_must_be_dict():
2828
config.load(config_data="test")
2929

3030

31+
def test_load_dump_extra_parameters():
32+
config = Configuration()
33+
34+
config.load_dump_extra_parameters({})
35+
assert config.mysqldump_params == ["--single-transaction"]
36+
assert config.pg_dump_params == []
37+
38+
with pytest.raises(ConfigurationError):
39+
config.load_dump_extra_parameters({"config": "test"})
40+
41+
config.load_dump_extra_parameters({"config": {}})
42+
assert config.mysqldump_params == ["--single-transaction"]
43+
assert config.pg_dump_params == []
44+
45+
with pytest.raises(ConfigurationError):
46+
config.load_dump_extra_parameters({"config": {
47+
"extra_parameters": "test"
48+
}})
49+
50+
with pytest.raises(ConfigurationError):
51+
config.load_dump_extra_parameters({"config": {
52+
"extra_parameters": [True]
53+
}})
54+
55+
with pytest.raises(ConfigurationError):
56+
config.load_dump_extra_parameters({"config": {
57+
"extra_parameters": {
58+
"mysqldump": "hernekeitto",
59+
},
60+
}})
61+
62+
with pytest.raises(ConfigurationError):
63+
config.load_dump_extra_parameters({"config": {
64+
"extra_parameters": {
65+
"pg_dump": "viina",
66+
},
67+
}})
68+
69+
config.load_dump_extra_parameters({"config": {
70+
"extra_parameters": {
71+
"mysqldump": ["--double-transaction"],
72+
"pg_dump": ["--exclude-table=something"],
73+
},
74+
}})
75+
assert config.mysqldump_params == ["--double-transaction"]
76+
assert config.pg_dump_params == ["--exclude-table=something"]
77+
78+
3179
def test_load_addon_packages():
3280
config = Configuration()
3381

database_sanitizer/tests/test_dump.py

+71-1
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,15 @@
55
import pytest
66

77
from database_sanitizer import dump
8+
from database_sanitizer.config import Configuration
89

910
EXPECTED_POPEN_KWARGS = {
1011
'mysql://User:Pass@HostName/Db': {
1112
'args': (
1213
'mysqldump --complete-insert --extended-insert'
13-
' --net_buffer_length=10240 -h hostname -u User Db').split(),
14+
' --net_buffer_length=10240 -h hostname -u User Db'
15+
' --single-transaction'
16+
).split(),
1417
'env': {'MYSQL_PWD': 'Pass'},
1518
'stdout': subprocess.PIPE,
1619
},
@@ -45,6 +48,73 @@ def test_run(mocked_popen, url):
4548
assert popen_kwargs == expected_popen_kwargs
4649

4750

51+
@mock.patch('subprocess.Popen')
52+
def test_run_with_mysql_extra_params(mocked_popen):
53+
mocked_popen.return_value.stdout = BytesIO(b'INPUT DUMP')
54+
output = StringIO()
55+
56+
url = "mysql://User:Pass@HostName/Db"
57+
config = Configuration()
58+
config.load({
59+
"config": {
60+
"extra_parameters": {
61+
"mysqldump": ["--double-transaction"]
62+
}
63+
}
64+
})
65+
66+
dump.run(url, output, config)
67+
68+
expected = {
69+
'args': (
70+
'mysqldump --complete-insert --extended-insert'
71+
' --net_buffer_length=10240 -h hostname -u User Db'
72+
' --double-transaction'
73+
).split(),
74+
'env': {'MYSQL_PWD': 'Pass'},
75+
'stdout': subprocess.PIPE,
76+
}
77+
78+
(popen_args, popen_kwargs) = mocked_popen.call_args
79+
expected_popen_args = (
80+
(expected.pop('args'),) if popen_args else ())
81+
assert popen_args == expected_popen_args
82+
assert popen_kwargs == expected
83+
84+
85+
@mock.patch('subprocess.Popen')
86+
def test_run_with_pg_dump_extra_params(mocked_popen):
87+
mocked_popen.return_value.stdout = BytesIO(b'INPUT DUMP')
88+
output = StringIO()
89+
90+
url = "postgres:///Db"
91+
config = Configuration()
92+
config.load({
93+
"config": {
94+
"extra_parameters": {
95+
"pg_dump": ["--exclude-table=something"]
96+
}
97+
}
98+
})
99+
100+
dump.run(url, output, config)
101+
102+
expected = {
103+
'args': tuple((
104+
'pg_dump --encoding=utf-8 --quote-all-identifiers'
105+
' --dbname postgres:///Db'
106+
' --exclude-table=something'
107+
).split()),
108+
'stdout': subprocess.PIPE,
109+
}
110+
111+
(popen_args, popen_kwargs) = mocked_popen.call_args
112+
expected_popen_args = (
113+
(expected.pop('args'),) if popen_args else ())
114+
assert popen_args == expected_popen_args
115+
assert popen_kwargs == expected
116+
117+
48118
@mock.patch('subprocess.Popen')
49119
def test_run_unknown_scheme(mocked_popen):
50120
with pytest.raises(ValueError) as excinfo:

0 commit comments

Comments
 (0)