diskette 0.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. diskette-0.3.0/LICENCE.txt +21 -0
  2. diskette-0.3.0/MANIFEST.in +13 -0
  3. diskette-0.3.0/PKG-INFO +92 -0
  4. diskette-0.3.0/README.rst +42 -0
  5. diskette-0.3.0/diskette/__init__.py +6 -0
  6. diskette-0.3.0/diskette/apps.py +7 -0
  7. diskette-0.3.0/diskette/contrib/__init__.py +0 -0
  8. diskette-0.3.0/diskette/contrib/composer/__init__.py +0 -0
  9. diskette-0.3.0/diskette/contrib/composer/collectors.py +9 -0
  10. diskette-0.3.0/diskette/contrib/composer/processors.py +23 -0
  11. diskette-0.3.0/diskette/contrib/django_configuration.py +42 -0
  12. diskette-0.3.0/diskette/core/__init__.py +0 -0
  13. diskette-0.3.0/diskette/core/applications/__init__.py +9 -0
  14. diskette-0.3.0/diskette/core/applications/definitions.py +333 -0
  15. diskette-0.3.0/diskette/core/applications/lookupstore.py +412 -0
  16. diskette-0.3.0/diskette/core/applications/store.py +16 -0
  17. diskette-0.3.0/diskette/core/defaults.py +9 -0
  18. diskette-0.3.0/diskette/core/dumper.py +496 -0
  19. diskette-0.3.0/diskette/core/handlers/__init__.py +8 -0
  20. diskette-0.3.0/diskette/core/handlers/dump.py +352 -0
  21. diskette-0.3.0/diskette/core/handlers/load.py +107 -0
  22. diskette-0.3.0/diskette/core/loader.py +248 -0
  23. diskette-0.3.0/diskette/core/serializers/__init__.py +10 -0
  24. diskette-0.3.0/diskette/core/serializers/dumpdata.py +175 -0
  25. diskette-0.3.0/diskette/core/serializers/loaddata.py +111 -0
  26. diskette-0.3.0/diskette/core/storages.py +137 -0
  27. diskette-0.3.0/diskette/exceptions.py +97 -0
  28. diskette-0.3.0/diskette/factories/__init__.py +6 -0
  29. diskette-0.3.0/diskette/factories/user.py +57 -0
  30. diskette-0.3.0/diskette/models/__init__.py +1 -0
  31. diskette-0.3.0/diskette/settings.py +43 -0
  32. diskette-0.3.0/diskette/urls.py +4 -0
  33. diskette-0.3.0/diskette/utils/__init__.py +0 -0
  34. diskette-0.3.0/diskette/utils/filesystem.py +23 -0
  35. diskette-0.3.0/diskette/utils/jsons.py +27 -0
  36. diskette-0.3.0/diskette/utils/lists.py +42 -0
  37. diskette-0.3.0/diskette/utils/loggers.py +111 -0
  38. diskette-0.3.0/diskette/utils/tests.py +265 -0
  39. diskette-0.3.0/diskette.egg-info/PKG-INFO +92 -0
  40. diskette-0.3.0/diskette.egg-info/SOURCES.txt +45 -0
  41. diskette-0.3.0/diskette.egg-info/dependency_links.txt +1 -0
  42. diskette-0.3.0/diskette.egg-info/requires.txt +27 -0
  43. diskette-0.3.0/diskette.egg-info/top_level.txt +2 -0
  44. diskette-0.3.0/diskette.egg-info/zip-safe +1 -0
  45. diskette-0.3.0/setup.cfg +104 -0
  46. diskette-0.3.0/setup.py +4 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023-2024, Emencia
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,13 @@
1
+ include MANIFEST.in
2
+ include LICENCE.txt
3
+ include README.rst
4
+
5
+ recursive-include diskette/templates *
6
+ recursive-include diskette/static *
7
+ recursive-include diskette/locale *
8
+
9
+ recursive-exclude docs *
10
+ recursive-exclude tests *
11
+ recursive-exclude sandbox *
12
+ recursive-exclude * __pycache__
13
+ recursive-exclude * *.py[co]
@@ -0,0 +1,92 @@
1
+ Metadata-Version: 2.1
2
+ Name: diskette
3
+ Version: 0.3.0
4
+ Summary: Export and import Django application data and medias
5
+ Home-page: https://github.com/emencia/diskette
6
+ Author: Emencia
7
+ Author-email: support@emencia.com
8
+ License: MIT
9
+ Project-URL: Source Code, https://github.com/emencia/diskette
10
+ Project-URL: Issue Tracker, https://github.com/emencia/diskette/issues
11
+ Project-URL: Changelog, https://diskette.readthedocs.io/en/latest/history.html
12
+ Project-URL: Documentation, https://diskette.readthedocs.io/
13
+ Keywords: Python,Django
14
+ Classifier: Development Status :: 4 - Beta
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Classifier: Natural Language :: English
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3.9
20
+ Classifier: Programming Language :: Python :: 3.10
21
+ Classifier: Framework :: Django
22
+ Classifier: Framework :: Django :: 4.0
23
+ Classifier: Framework :: Django :: 4.1
24
+ Classifier: Framework :: Django :: 4.2
25
+ Requires-Python: >=3.9
26
+ Description-Content-Type: text/x-rst
27
+ License-File: LICENCE.txt
28
+ Requires-Dist: Django<5.0,>=4.0
29
+ Requires-Dist: django-sendfile2>=0.7.0
30
+ Requires-Dist: datalookup>=1.0.0
31
+ Provides-Extra: dev
32
+ Requires-Dist: pytest; extra == "dev"
33
+ Requires-Dist: pytest-django; extra == "dev"
34
+ Requires-Dist: factory-boy; extra == "dev"
35
+ Requires-Dist: pyquery; extra == "dev"
36
+ Requires-Dist: freezegun; extra == "dev"
37
+ Provides-Extra: quality
38
+ Requires-Dist: flake8; extra == "quality"
39
+ Requires-Dist: tox; extra == "quality"
40
+ Provides-Extra: doc
41
+ Requires-Dist: sphinx; extra == "doc"
42
+ Requires-Dist: furo==2023.7.26; extra == "doc"
43
+ Requires-Dist: sphinx-copybutton==0.5.2; extra == "doc"
44
+ Requires-Dist: tabulate; extra == "doc"
45
+ Requires-Dist: project-composer; extra == "doc"
46
+ Provides-Extra: doc-live
47
+ Requires-Dist: livereload; extra == "doc-live"
48
+ Provides-Extra: release
49
+ Requires-Dist: twine; extra == "release"
50
+
51
+ .. _Python: https://www.python.org/
52
+ .. _Django: https://www.djangoproject.com/
53
+ .. _django-sendfile2: https://github.com/moggers87/django-sendfile2
54
+ .. _datalookup: https://datalookup.readthedocs.io/
55
+
56
+ ========
57
+ Diskette
58
+ ========
59
+
60
+ Export and import Django application datas and medias.
61
+
62
+
63
+ Features
64
+ ********
65
+
66
+ * Based on *Django apps* to know about applications and their models;
67
+ * Application datas are dumped with Django ``dumpdata`` command as JSON fixtures, dumps
68
+ can be naturally loaded in any database type using Django command ``loaddata``;
69
+ * Define application to be dumped with multiple options;
70
+ * Advanced data drainage for undefined applications;
71
+ * Media archiving is done through *Storages* (not *Django storages*) that can be
72
+ whatever directory you need to backup;
73
+ * Many excluding rules for datas and storages to avoid useless content in archive;
74
+ * Build a complete archive that can be automatically loaded with Diskette or manually;
75
+ * Support models made with ``django-polymorphic``;
76
+
77
+
78
+ Dependancies
79
+ ************
80
+
81
+ * `Python`_>=3.9;
82
+ * `Django`_>=4.0,<5.0;
83
+ * `django-sendfile2`_>=0.7.0;
84
+ * `datalookup`_>=1.0.0;
85
+
86
+
87
+ Links
88
+ *****
89
+
90
+ * Read the documentation on `Read the docs <https://diskette.readthedocs.io/>`_;
91
+ * Download its `PyPi package <https://pypi.python.org/pypi/diskette>`_;
92
+ * Clone it on its `Github repository <https://github.com/emencia/diskette>`_;
@@ -0,0 +1,42 @@
1
+ .. _Python: https://www.python.org/
2
+ .. _Django: https://www.djangoproject.com/
3
+ .. _django-sendfile2: https://github.com/moggers87/django-sendfile2
4
+ .. _datalookup: https://datalookup.readthedocs.io/
5
+
6
+ ========
7
+ Diskette
8
+ ========
9
+
10
+ Export and import Django application datas and medias.
11
+
12
+
13
+ Features
14
+ ********
15
+
16
+ * Based on *Django apps* to know about applications and their models;
17
+ * Application datas are dumped with Django ``dumpdata`` command as JSON fixtures, dumps
18
+ can be naturally loaded in any database type using Django command ``loaddata``;
19
+ * Define application to be dumped with multiple options;
20
+ * Advanced data drainage for undefined applications;
21
+ * Media archiving is done through *Storages* (not *Django storages*) that can be
22
+ whatever directory you need to backup;
23
+ * Many excluding rules for datas and storages to avoid useless content in archive;
24
+ * Build a complete archive that can be automatically loaded with Diskette or manually;
25
+ * Support models made with ``django-polymorphic``;
26
+
27
+
28
+ Dependancies
29
+ ************
30
+
31
+ * `Python`_>=3.9;
32
+ * `Django`_>=4.0,<5.0;
33
+ * `django-sendfile2`_>=0.7.0;
34
+ * `datalookup`_>=1.0.0;
35
+
36
+
37
+ Links
38
+ *****
39
+
40
+ * Read the documentation on `Read the docs <https://diskette.readthedocs.io/>`_;
41
+ * Download its `PyPi package <https://pypi.python.org/pypi/diskette>`_;
42
+ * Clone it on its `Github repository <https://github.com/emencia/diskette>`_;
@@ -0,0 +1,6 @@
1
+ """A system to compose project parts from applications enabled in a manifest"""
2
+ from importlib.metadata import version
3
+
4
+
5
+ __pkgname__ = "diskette"
6
+ __version__ = version(__pkgname__)
@@ -0,0 +1,7 @@
1
+ from django.apps import AppConfig
2
+
3
+
4
+ class DisketteConfig(AppConfig):
5
+ name = "diskette"
6
+ verbose_name = "Diskette"
7
+ default_auto_field = "django.db.models.AutoField"
File without changes
File without changes
@@ -0,0 +1,9 @@
1
+ class DisketteDefinitionsCollector:
2
+ """
3
+ Diskette definition collector for 'project-composer'.
4
+ """
5
+ def define(self):
6
+ return []
7
+
8
+ def get_definitions(self):
9
+ return [d for d in self.define()]
@@ -0,0 +1,23 @@
1
+ from project_composer.processors import ClassProcessor
2
+
3
+
4
+ class DisketteDefinitionsProcessor(ClassProcessor):
5
+ """
6
+ 'project-composer' Processor to collect Diskette application definitions from
7
+ enabled applications.
8
+ """
9
+ def get_module_path(self, name):
10
+ """
11
+ Return a Python path for a module name.
12
+
13
+ Arguments:
14
+ name (string): Module name.
15
+
16
+ Returns:
17
+ string: Module name prefixed with repository path if it is not empty else
18
+ returns just the module name.
19
+ """
20
+ return "{base}.{part}".format(
21
+ base=self.composer.get_application_base_module_path(name),
22
+ part="disk",
23
+ )
@@ -0,0 +1,42 @@
1
+ """
2
+ .. _django-configuration: https://django-configurations.readthedocs.io/en/stable/
3
+ """
4
+ from ..settings import (
5
+ DISKETTE_APPS,
6
+ DISKETTE_STORAGES,
7
+ DISKETTE_STORAGES_EXCLUDES,
8
+ DISKETTE_DUMP_PATH,
9
+ DISKETTE_DUMP_FILENAME,
10
+ DISKETTE_LOAD_STORAGES_PATH,
11
+ )
12
+
13
+
14
+ class DisketteDefaultSettings:
15
+ """
16
+ Default Diskette settings class to use with a `django-configuration`_ class.
17
+
18
+ You could use it like so: ::
19
+
20
+ from configurations import Configuration
21
+ from diskette.contrib.django_configuration import DisketteDefaultSettings
22
+
23
+ class Dev(DisketteDefaultSettings, Configuration):
24
+ DEBUG = True
25
+
26
+ DISKETTE_DUMP_FILENAME = "foo.tar.gz"
27
+
28
+ This will override only the setting ``DISKETTE_DUMP_FILENAME``, all other
29
+ Diskette settings will have the default values from ``diskette.settings``.
30
+ """
31
+
32
+ DISKETTE_APPS = DISKETTE_APPS
33
+
34
+ DISKETTE_STORAGES = DISKETTE_STORAGES
35
+
36
+ DISKETTE_STORAGES_EXCLUDES = DISKETTE_STORAGES_EXCLUDES
37
+
38
+ DISKETTE_DUMP_PATH = DISKETTE_DUMP_PATH
39
+
40
+ DISKETTE_DUMP_FILENAME = DISKETTE_DUMP_FILENAME
41
+
42
+ DISKETTE_LOAD_STORAGES_PATH = DISKETTE_LOAD_STORAGES_PATH
File without changes
@@ -0,0 +1,9 @@
1
+ from .definitions import ApplicationConfig, DrainApplicationConfig
2
+ from .lookupstore import DjangoAppLookupStore
3
+
4
+
5
+ __all__ = [
6
+ "ApplicationConfig",
7
+ "DjangoAppLookupStore",
8
+ "DrainApplicationConfig",
9
+ ]
@@ -0,0 +1,333 @@
1
+ from pathlib import Path
2
+
3
+ from django.utils.text import slugify
4
+
5
+ from ...exceptions import ApplicationConfigError
6
+ from ..defaults import DEFAULT_FORMAT, AVAILABLE_FORMATS
7
+
8
+ from .store import get_appstore
9
+
10
+
11
+ appstore = get_appstore()
12
+
13
+
14
+ class ApplicationConfig:
15
+ """
16
+ Application model to validate and store application details.
17
+
18
+ TODO: Another name would better to avoid mental clash with Django "AppConfig".
19
+ ApplicationModel (+2) ? ApplicationDataDef (0)? ApplicationDefinition (+3) ?
20
+ DataDefinition (+3) ?
21
+
22
+ Arguments:
23
+ name (string): Application name, almost anything but it may be slugified for
24
+ internal usages so avoid too much longer text and special characters.
25
+ models (list): List of labels. A label is either an application label like
26
+ ``auth`` or a full model label like ``auth.user``. Labels are validated,
27
+ they must exists in Django application registry.
28
+
29
+ Keyword Arguments:
30
+ filename (string): The filename to use if application dump is to be written in
31
+ a file. The filename also determine the format used to dump data. If you
32
+ want another format that the default one you will have to define
33
+ it from the filename even you don't plan to write dump to a file.
34
+ Finally if not given, the filename will be automatically defined with
35
+ slugified ``name`` with default format.
36
+ excludes (list): The list of excluded model labels that won't be collected
37
+ into the application dump. Currently, the excluded labels are not
38
+ validated.
39
+ natural_foreign (boolean): Enable usage of natural foreign key.
40
+ natural_primary (boolean): Enable usage of natural primary key.
41
+ comments (string): Free text not used internally.
42
+ allow_drain (boolean): Define if application allows its excluded models to be
43
+ drained. Default is ``False`` to avoid implicit draining of data that may
44
+ not be wanted.
45
+ dump_command (string): Custom dump command to use for this specific application
46
+ instead of default ``dumpdata``. If you have models that use
47
+ ``django-polymorphic`` you should give value ``polymorphic_dumpdata`` here.
48
+
49
+ Attributes:
50
+ is_drain (boolean): Declare application as a special drain application. This
51
+ should always be ``False`` for a common application, ``True`` value is
52
+ reserved to ``DrainApplicationConfig``.
53
+ """
54
+ CONFIG_ATTRS = [
55
+ "name", "models", "excludes", "retention", "natural_foreign", "natural_primary",
56
+ "comments", "filename", "is_drain", "allow_drain", "dump_command",
57
+ ]
58
+ OPTIONS_ATTRS = [
59
+ "models", "excludes", "natural_foreign", "natural_primary", "filename",
60
+ ]
61
+
62
+ def __init__(self, name, models=[], excludes=None, natural_foreign=False,
63
+ natural_primary=False, comments=None, filename=None,
64
+ is_drain=None, allow_drain=False, dump_command=None):
65
+ self.name = name
66
+ self._models = [models] if isinstance(models, str) else models
67
+ self._excludes = excludes or []
68
+ self.natural_foreign = natural_foreign
69
+ self.natural_primary = natural_primary
70
+ self.comments = comments
71
+ self.filename = filename or self.get_filename()
72
+ self.allow_drain = allow_drain
73
+ self.dump_command = dump_command
74
+ self.is_drain = False
75
+
76
+ def __repr__(self):
77
+ return "<{klass}: {name}>".format(
78
+ klass=self.__class__.__name__,
79
+ name=self.name
80
+ )
81
+
82
+ @property
83
+ def models(self):
84
+ """
85
+ List all fully qualified model labels to include, either implicitely and
86
+ explicitely from given ``models`` argument.
87
+
88
+ NOTE: models, excludes and retention attributes may be cached since they have
89
+ no reason to change.
90
+
91
+ Returns:
92
+ list: Fully qualified model labels.
93
+ """
94
+ return [
95
+ model.label
96
+ for model in appstore.get_models_inclusions(
97
+ self._models, excludes=self._excludes
98
+ )
99
+ ]
100
+
101
+ @property
102
+ def excludes(self):
103
+ """
104
+ List all fully qualified model labels to exclude, either implicitely and
105
+ explicitely from given ``_excludes`` argument.
106
+
107
+ Returns:
108
+ list: Fully qualified model labels.
109
+ """
110
+ return [
111
+ model.label
112
+ for model in appstore.get_models_exclusions(
113
+ self._models, excludes=self._excludes
114
+ )
115
+ ]
116
+
117
+ @property
118
+ def retention(self):
119
+ """
120
+ List all fully qualified model labels that are not allowed to be drained from
121
+ this application.
122
+
123
+ Included models are never allowed to be drained and exclusions models may be
124
+ allowed if ``allow_drain`` is enabled.
125
+
126
+ Returns:
127
+ list: Fully qualified model labels that won't be allowed to be drained.
128
+ This means labels from ``models`` on defaut and possibly the
129
+ ``excludes`` one also if application allows for drainage.
130
+ """
131
+ if not self.allow_drain:
132
+ return self.models + self.excludes
133
+
134
+ return self.models
135
+
136
+ def get_filename(self, format_extension=None):
137
+ """
138
+ Automatically determine filename from Application name and with a format
139
+ extension name.
140
+
141
+ Keyword Arguments:
142
+ format_extension (string): Custom extension to use if given.
143
+
144
+ Returns
145
+ string: Filename.
146
+ """
147
+ return slugify(self.name) + "." + (format_extension or DEFAULT_FORMAT)
148
+
149
+ def as_config(self):
150
+ """
151
+ Returns Application configuration suitable for dump history.
152
+
153
+ Keyword Arguments:
154
+ name (boolean): To include or not the name into the dict.
155
+ commented (boolean): To include or not the comments into the dict.
156
+
157
+ Returns
158
+ dict: Application data.
159
+ """
160
+ return {
161
+ name: getattr(self, name)
162
+ for name in self.CONFIG_ATTRS
163
+ }
164
+
165
+ def as_options(self):
166
+ """
167
+ Returns Application options suitable to pass to dumpdata command.
168
+
169
+ Keyword Arguments:
170
+ name (boolean): To include or not the name into the dict.
171
+ commented (boolean): To include or not the comments into the dict.
172
+
173
+ Returns
174
+ dict: Application data.
175
+ """
176
+ return {
177
+ name: getattr(self, name)
178
+ for name in self.OPTIONS_ATTRS
179
+ }
180
+
181
+ def validate_includes(self):
182
+ """
183
+ Validate include labels from ``_models`` attribute.
184
+ """
185
+ # Models are required but not for an application drain object
186
+ if not self._models and self.is_drain is False:
187
+ msg = "{obj}: 'models' must not be an empty value."
188
+ raise ApplicationConfigError(msg.format(
189
+ obj=self.__repr__(),
190
+ ))
191
+
192
+ unknow_apps, unknow_models = appstore.check_unexisting_labels(self._models)
193
+
194
+ if len(unknow_apps) > 0:
195
+ msg = (
196
+ "{obj}: Some given app labels to include does not exists: {labels}"
197
+ )
198
+ raise ApplicationConfigError(msg.format(
199
+ obj=self.__repr__(),
200
+ labels=", ".join(unknow_apps),
201
+ ))
202
+
203
+ if len(unknow_models) > 0:
204
+ msg = (
205
+ "{obj}: Some given models labels to include does not exists: {labels}"
206
+ )
207
+ raise ApplicationConfigError(msg.format(
208
+ obj=self.__repr__(),
209
+ labels=", ".join(unknow_models),
210
+ ))
211
+
212
+ def validate_excludes(self):
213
+ """
214
+ Validate exclude labels from ``excludes`` attribute.
215
+ """
216
+ if not isinstance(self._excludes, list):
217
+ msg = "{obj}: 'excludes' argument must be a list."
218
+ raise ApplicationConfigError(msg.format(
219
+ obj=self.__repr__(),
220
+ ))
221
+ else:
222
+ errors = appstore.is_fully_qualified_labels(self._excludes)
223
+ if errors:
224
+ msg = (
225
+ "{obj}: 'excludes' argument can only contains fully qualified "
226
+ "labels (like 'foo.bar') these ones are invalid: {labels}"
227
+ )
228
+ raise ApplicationConfigError(msg.format(
229
+ obj=self.__repr__(),
230
+ labels=", ".join(errors),
231
+ ))
232
+
233
+ unknow_apps, unknow_models = appstore.check_unexisting_labels(self._excludes)
234
+
235
+ if len(unknow_apps) > 0:
236
+ msg = (
237
+ "{obj}: Some given app labels to exclude does not exists: {labels}"
238
+ )
239
+ raise ApplicationConfigError(msg.format(
240
+ obj=self.__repr__(),
241
+ labels=", ".join(unknow_apps),
242
+ ))
243
+
244
+ if len(unknow_models) > 0:
245
+ msg = (
246
+ "{obj}: Some given models labels to exclude does not exists: {labels}"
247
+ )
248
+ raise ApplicationConfigError(msg.format(
249
+ obj=self.__repr__(),
250
+ labels=", ".join(unknow_models),
251
+ ))
252
+
253
+ def validate_filename(self):
254
+ # Filename must have a file extension to discover serialization format
255
+ extension = Path(self.filename).suffix
256
+ if not extension:
257
+ msg = (
258
+ "{obj}: Given file name '{filename}' must have a file extension to "
259
+ "discover format."
260
+ )
261
+ raise ApplicationConfigError(msg.format(
262
+ obj=self.__repr__(),
263
+ filename=self.filename,
264
+ ))
265
+ # File extension must correspond to an allowed format
266
+ else:
267
+ # Remove leading dot
268
+ extension = extension[1:]
269
+ if extension not in AVAILABLE_FORMATS:
270
+ msg = (
271
+ "{obj}: Given file name '{filename}' must use a file extension "
272
+ "from allowed formats: {formats}"
273
+ )
274
+ raise ApplicationConfigError(msg.format(
275
+ obj=self.__repr__(),
276
+ filename=self.filename,
277
+ formats=", ".join(AVAILABLE_FORMATS),
278
+ ))
279
+
280
+ def validate(self):
281
+ """
282
+ Validate Application options.
283
+
284
+ Raises:
285
+ ApplicationConfigError: In case of invalid values from options.
286
+ """
287
+ self.validate_filename()
288
+ self.validate_includes()
289
+ self.validate_excludes()
290
+
291
+
292
+ class DrainApplicationConfig(ApplicationConfig):
293
+ """
294
+ Special application to drain remaining models from apps.
295
+
296
+ On default a drain will dump anything that have not been defined from apps. Its
297
+ base goal is to dump data from undefined applications.
298
+
299
+ Attributes:
300
+ drain_excluded (boolean): If enabled, the drain will accept to drain exclusion
301
+ from applications which allow it. Else the drain will exclude also the
302
+ application exclusion. Default is disabled.
303
+ """
304
+ CONFIG_ATTRS = [
305
+ "name", "models", "excludes", "natural_foreign", "natural_primary", "comments",
306
+ "filename", "is_drain", "drain_excluded", "dump_command",
307
+ ]
308
+ OPTIONS_ATTRS = [
309
+ "models", "excludes", "natural_foreign", "natural_primary", "filename",
310
+ ]
311
+
312
+ def __init__(self, *args, **kwargs):
313
+ self.drain_excluded = kwargs.pop("drain_excluded", False)
314
+
315
+ super().__init__(*args, **kwargs)
316
+
317
+ # Drain never allow for any inclusion
318
+ self._models = []
319
+ # Force as a drain only
320
+ self.is_drain = True
321
+ # It is not allowed to be drained itself
322
+ self.allow_drain = False
323
+
324
+ @property
325
+ def excludes(self):
326
+ """
327
+ Just returns exclude labels as given since drain excludes are meaningful
328
+ enough.
329
+
330
+ Returns:
331
+ list: Fully qualified model labels.
332
+ """
333
+ return self._excludes