mantis-cli 22.0.0__tar.gz → 22.0.2__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. {mantis_cli-22.0.0/mantis_cli.egg-info → mantis_cli-22.0.2}/PKG-INFO +4 -22
  2. mantis_cli-22.0.2/mantis/__init__.py +1 -0
  3. {mantis_cli-22.0.0 → mantis_cli-22.0.2}/mantis/extensions/django.py +3 -1
  4. {mantis_cli-22.0.0 → mantis_cli-22.0.2}/mantis/helpers.py +21 -0
  5. {mantis_cli-22.0.0 → mantis_cli-22.0.2}/mantis/managers.py +4 -6
  6. {mantis_cli-22.0.0 → mantis_cli-22.0.2/mantis_cli.egg-info}/PKG-INFO +5 -23
  7. {mantis_cli-22.0.0 → mantis_cli-22.0.2}/mantis_cli.egg-info/SOURCES.txt +1 -0
  8. {mantis_cli-22.0.0 → mantis_cli-22.0.2}/mantis_cli.egg-info/entry_points.txt +1 -0
  9. mantis_cli-22.0.2/tests/test_helpers.py +128 -0
  10. mantis_cli-22.0.0/mantis/__init__.py +0 -1
  11. {mantis_cli-22.0.0 → mantis_cli-22.0.2}/LICENSE +0 -0
  12. {mantis_cli-22.0.0 → mantis_cli-22.0.2}/MANIFEST.in +0 -0
  13. {mantis_cli-22.0.0 → mantis_cli-22.0.2}/README.md +0 -0
  14. {mantis_cli-22.0.0 → mantis_cli-22.0.2}/mantis/__main__.py +0 -0
  15. {mantis_cli-22.0.0 → mantis_cli-22.0.2}/mantis/app.py +0 -0
  16. {mantis_cli-22.0.0 → mantis_cli-22.0.2}/mantis/command_line.py +0 -0
  17. {mantis_cli-22.0.0 → mantis_cli-22.0.2}/mantis/commands/__init__.py +0 -0
  18. {mantis_cli-22.0.0 → mantis_cli-22.0.2}/mantis/commands/compose.py +0 -0
  19. {mantis_cli-22.0.0 → mantis_cli-22.0.2}/mantis/commands/configuration.py +0 -0
  20. {mantis_cli-22.0.0 → mantis_cli-22.0.2}/mantis/commands/connection.py +0 -0
  21. {mantis_cli-22.0.0 → mantis_cli-22.0.2}/mantis/commands/containers.py +0 -0
  22. {mantis_cli-22.0.0 → mantis_cli-22.0.2}/mantis/commands/core.py +0 -0
  23. {mantis_cli-22.0.0 → mantis_cli-22.0.2}/mantis/commands/django.py +0 -0
  24. {mantis_cli-22.0.0 → mantis_cli-22.0.2}/mantis/commands/images.py +0 -0
  25. {mantis_cli-22.0.0 → mantis_cli-22.0.2}/mantis/commands/nginx.py +0 -0
  26. {mantis_cli-22.0.0 → mantis_cli-22.0.2}/mantis/commands/postgres.py +0 -0
  27. {mantis_cli-22.0.0 → mantis_cli-22.0.2}/mantis/commands/secrets.py +0 -0
  28. {mantis_cli-22.0.0 → mantis_cli-22.0.2}/mantis/commands/services.py +0 -0
  29. {mantis_cli-22.0.0 → mantis_cli-22.0.2}/mantis/commands/volumes.py +0 -0
  30. {mantis_cli-22.0.0 → mantis_cli-22.0.2}/mantis/config.py +0 -0
  31. {mantis_cli-22.0.0 → mantis_cli-22.0.2}/mantis/cryptography.py +0 -0
  32. {mantis_cli-22.0.0 → mantis_cli-22.0.2}/mantis/environment.py +0 -0
  33. {mantis_cli-22.0.0 → mantis_cli-22.0.2}/mantis/extensions/__init__.py +0 -0
  34. {mantis_cli-22.0.0 → mantis_cli-22.0.2}/mantis/extensions/nginx.py +0 -0
  35. {mantis_cli-22.0.0 → mantis_cli-22.0.2}/mantis/extensions/postgres.py +0 -0
  36. {mantis_cli-22.0.0 → mantis_cli-22.0.2}/mantis/mantis.tpl +0 -0
  37. {mantis_cli-22.0.0 → mantis_cli-22.0.2}/mantis/schema.py +0 -0
  38. {mantis_cli-22.0.0 → mantis_cli-22.0.2}/mantis_cli.egg-info/dependency_links.txt +0 -0
  39. {mantis_cli-22.0.0 → mantis_cli-22.0.2}/mantis_cli.egg-info/requires.txt +0 -0
  40. {mantis_cli-22.0.0 → mantis_cli-22.0.2}/mantis_cli.egg-info/top_level.txt +0 -0
  41. {mantis_cli-22.0.0 → mantis_cli-22.0.2}/setup.cfg +0 -0
  42. {mantis_cli-22.0.0 → mantis_cli-22.0.2}/setup.py +0 -0
  43. {mantis_cli-22.0.0 → mantis_cli-22.0.2}/tests/__init__.py +0 -0
  44. {mantis_cli-22.0.0 → mantis_cli-22.0.2}/tests/test_command_line.py +0 -0
  45. {mantis_cli-22.0.0 → mantis_cli-22.0.2}/tests/test_config.py +0 -0
  46. {mantis_cli-22.0.0 → mantis_cli-22.0.2}/tests/test_managers.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.1
2
2
  Name: mantis_cli
3
- Version: 22.0.0
3
+ Version: 22.0.2
4
4
  Summary: Management command to build and deploy webapps, especially based on Django
5
5
  Home-page: https://github.com/PragmaticMates/mantis-cli
6
6
  Author: Erik Telepovský
@@ -9,6 +9,7 @@ Maintainer: Pragmatic Mates
9
9
  Maintainer-email: info@pragmaticmates.com
10
10
  License: GNU General Public License (GPL)
11
11
  Keywords: management deployment docker command
12
+ Platform: UNKNOWN
12
13
  Classifier: Programming Language :: Python
13
14
  Classifier: Operating System :: OS Independent
14
15
  Classifier: Environment :: Web Environment
@@ -18,26 +19,6 @@ Classifier: License :: OSI Approved :: GNU General Public License (GPL)
18
19
  Classifier: Development Status :: 5 - Production/Stable
19
20
  Description-Content-Type: text/markdown
20
21
  License-File: LICENSE
21
- Requires-Dist: cffi
22
- Requires-Dist: cryptography
23
- Requires-Dist: pycryptodome
24
- Requires-Dist: pydantic
25
- Requires-Dist: PyYAML
26
- Requires-Dist: rich
27
- Requires-Dist: typer
28
- Dynamic: author
29
- Dynamic: author-email
30
- Dynamic: classifier
31
- Dynamic: description
32
- Dynamic: description-content-type
33
- Dynamic: home-page
34
- Dynamic: keywords
35
- Dynamic: license
36
- Dynamic: license-file
37
- Dynamic: maintainer
38
- Dynamic: maintainer-email
39
- Dynamic: requires-dist
40
- Dynamic: summary
41
22
 
42
23
  # mantis-cli
43
24
 
@@ -481,3 +462,4 @@ Works as follows:
481
462
  ## Release notes
482
463
 
483
464
  Mantis uses semantic versioning. See more in [changelog](https://github.com/PragmaticMates/mantis-cli/blob/master/CHANGES.md).
465
+
@@ -0,0 +1 @@
1
+ VERSION = '22.0.2'
@@ -1,3 +1,4 @@
1
+ import sys
1
2
  import time
2
3
  from typing import Optional
3
4
 
@@ -67,7 +68,8 @@ class Django():
67
68
  CLI.info('Django manage...')
68
69
  args_str = ' '.join(args) if args else ''
69
70
  full_cmd = f'{cmd} {args_str}'.strip()
70
- self.docker(f'exec -ti {container} python manage.py {full_cmd}')
71
+ tty_flag = 't' if sys.stdin.isatty() else ''
72
+ self.docker(f'exec -i{tty_flag} {container} python manage.py {full_cmd}')
71
73
 
72
74
  def send_test_email(self):
73
75
  """Sends test email to admins"""
@@ -123,6 +123,27 @@ def random_string(n=10):
123
123
  chars = string.ascii_lowercase + string.ascii_uppercase + string.digits
124
124
  return ''.join(random.choice(chars) for _ in range(n))
125
125
 
126
+
127
+ def merge_defaults(defaults, overrides):
128
+ """
129
+ Deep-merges user config over template defaults.
130
+
131
+ dict.update() is shallow, so declaring a single key of a section used to drop the rest
132
+ of that section's defaults: {"compose": {"command": "docker compose"}} left compose.folder
133
+ undefined and raised KeyError later on. Only mappings recurse — lists and scalars from
134
+ the user config replace the default outright.
135
+ """
136
+ merged = dict(defaults)
137
+
138
+ for key, value in overrides.items():
139
+ if isinstance(value, dict) and isinstance(merged.get(key), dict):
140
+ merged[key] = merge_defaults(merged[key], value)
141
+ else:
142
+ merged[key] = value
143
+
144
+ return merged
145
+
146
+
126
147
  def merge_json(obj1, obj2):
127
148
  # Base case: if both values are dictionaries, merge recursively
128
149
  if isinstance(obj1, dict) and isinstance(obj2, dict):
@@ -17,7 +17,7 @@ from rich.table import Table
17
17
 
18
18
  from mantis.cryptography import Crypto
19
19
  from mantis.environment import Environment
20
- from mantis.helpers import CLI, import_string, merge_json
20
+ from mantis.helpers import CLI, import_string, merge_defaults, merge_json
21
21
  from mantis.config import find_config, load_config, check_config, load_template_config, DEFAULT_ENV_FOLDER
22
22
 
23
23
 
@@ -146,11 +146,9 @@ class AbstractManager(object):
146
146
  # Load config template file
147
147
  defaults = load_template_config()
148
148
 
149
- # Merge custom config and default values
150
- defaults.update(config)
151
-
152
- # Save merged config to variable
153
- self.config = defaults.copy()
149
+ # Merge custom config over default values. Deep merge, so declaring one key of a
150
+ # section keeps the rest of that section's defaults.
151
+ self.config = merge_defaults(defaults, config)
154
152
 
155
153
  # Detect single connection mode (connection string instead of connections dict)
156
154
  has_single_connection = self.config.get('connection') is not None
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
2
- Name: mantis_cli
3
- Version: 22.0.0
1
+ Metadata-Version: 2.1
2
+ Name: mantis-cli
3
+ Version: 22.0.2
4
4
  Summary: Management command to build and deploy webapps, especially based on Django
5
5
  Home-page: https://github.com/PragmaticMates/mantis-cli
6
6
  Author: Erik Telepovský
@@ -9,6 +9,7 @@ Maintainer: Pragmatic Mates
9
9
  Maintainer-email: info@pragmaticmates.com
10
10
  License: GNU General Public License (GPL)
11
11
  Keywords: management deployment docker command
12
+ Platform: UNKNOWN
12
13
  Classifier: Programming Language :: Python
13
14
  Classifier: Operating System :: OS Independent
14
15
  Classifier: Environment :: Web Environment
@@ -18,26 +19,6 @@ Classifier: License :: OSI Approved :: GNU General Public License (GPL)
18
19
  Classifier: Development Status :: 5 - Production/Stable
19
20
  Description-Content-Type: text/markdown
20
21
  License-File: LICENSE
21
- Requires-Dist: cffi
22
- Requires-Dist: cryptography
23
- Requires-Dist: pycryptodome
24
- Requires-Dist: pydantic
25
- Requires-Dist: PyYAML
26
- Requires-Dist: rich
27
- Requires-Dist: typer
28
- Dynamic: author
29
- Dynamic: author-email
30
- Dynamic: classifier
31
- Dynamic: description
32
- Dynamic: description-content-type
33
- Dynamic: home-page
34
- Dynamic: keywords
35
- Dynamic: license
36
- Dynamic: license-file
37
- Dynamic: maintainer
38
- Dynamic: maintainer-email
39
- Dynamic: requires-dist
40
- Dynamic: summary
41
22
 
42
23
  # mantis-cli
43
24
 
@@ -481,3 +462,4 @@ Works as follows:
481
462
  ## Release notes
482
463
 
483
464
  Mantis uses semantic versioning. See more in [changelog](https://github.com/PragmaticMates/mantis-cli/blob/master/CHANGES.md).
465
+
@@ -40,4 +40,5 @@ mantis_cli.egg-info/top_level.txt
40
40
  tests/__init__.py
41
41
  tests/test_command_line.py
42
42
  tests/test_config.py
43
+ tests/test_helpers.py
43
44
  tests/test_managers.py
@@ -1,2 +1,3 @@
1
1
  [console_scripts]
2
2
  mantis = mantis.command_line:run
3
+
@@ -0,0 +1,128 @@
1
+ """Tests for helpers module - config default merging."""
2
+ import pytest
3
+
4
+ from mantis.config import load_template_config
5
+ from mantis.helpers import merge_defaults
6
+
7
+
8
+ class TestMergeDefaults:
9
+ """Tests for merge_defaults function."""
10
+
11
+ def test_partial_section_keeps_sibling_defaults(self):
12
+ """Declaring one key of a section must not drop the rest of it."""
13
+ defaults = {'compose': {'command': 'docker-compose', 'folder': '<MANTIS>/../compose'}}
14
+ overrides = {'compose': {'command': 'docker compose'}}
15
+
16
+ merged = merge_defaults(defaults, overrides)
17
+
18
+ assert merged['compose'] == {
19
+ 'command': 'docker compose',
20
+ 'folder': '<MANTIS>/../compose',
21
+ }
22
+
23
+ def test_override_wins_over_default(self):
24
+ """User values replace defaults of the same key."""
25
+ defaults = {'project_path': '~', 'compose': {'command': 'docker-compose'}}
26
+ overrides = {'project_path': '~/public_html/app/', 'compose': {'command': 'docker compose'}}
27
+
28
+ merged = merge_defaults(defaults, overrides)
29
+
30
+ assert merged['project_path'] == '~/public_html/app/'
31
+ assert merged['compose']['command'] == 'docker compose'
32
+
33
+ def test_untouched_sections_survive(self):
34
+ """Sections the user never mentions keep their defaults."""
35
+ defaults = {
36
+ 'compose': {'command': 'docker-compose'},
37
+ 'encryption': {'deterministic': True, 'folder': '<MANTIS>'},
38
+ }
39
+ overrides = {'compose': {'command': 'docker compose'}}
40
+
41
+ merged = merge_defaults(defaults, overrides)
42
+
43
+ assert merged['encryption'] == {'deterministic': True, 'folder': '<MANTIS>'}
44
+
45
+ def test_lists_are_replaced_not_concatenated(self):
46
+ """zero_downtime must be the user's list, not defaults + user."""
47
+ defaults = {'zero_downtime': ['web']}
48
+ overrides = {'zero_downtime': ['backend']}
49
+
50
+ merged = merge_defaults(defaults, overrides)
51
+
52
+ assert merged['zero_downtime'] == ['backend']
53
+
54
+ def test_new_keys_are_added(self):
55
+ """Keys absent from the defaults come through."""
56
+ defaults = {'project_path': '~'}
57
+ overrides = {'connections': {'production': 'ssh://user@host:22'}}
58
+
59
+ merged = merge_defaults(defaults, overrides)
60
+
61
+ assert merged['connections'] == {'production': 'ssh://user@host:22'}
62
+ assert merged['project_path'] == '~'
63
+
64
+ def test_nested_dicts_recurse(self):
65
+ """Merging descends more than one level."""
66
+ defaults = {'build': {'tool': 'compose', 'args': {'a': '1', 'b': '2'}}}
67
+ overrides = {'build': {'args': {'b': '99'}}}
68
+
69
+ merged = merge_defaults(defaults, overrides)
70
+
71
+ assert merged['build'] == {'tool': 'compose', 'args': {'a': '1', 'b': '99'}}
72
+
73
+ def test_scalar_replacing_dict(self):
74
+ """A scalar override replaces a dict default rather than recursing."""
75
+ defaults = {'connection': {'nested': True}}
76
+ overrides = {'connection': 'ssh://user@host:22'}
77
+
78
+ merged = merge_defaults(defaults, overrides)
79
+
80
+ assert merged['connection'] == 'ssh://user@host:22'
81
+
82
+ def test_inputs_are_not_mutated(self):
83
+ """Merging must not write back into the template defaults."""
84
+ defaults = {'compose': {'command': 'docker-compose', 'folder': 'x'}}
85
+ overrides = {'compose': {'command': 'docker compose'}}
86
+
87
+ merge_defaults(defaults, overrides)
88
+
89
+ assert defaults == {'compose': {'command': 'docker-compose', 'folder': 'x'}}
90
+
91
+ def test_empty_overrides_returns_defaults(self):
92
+ """No user config means the template stands as-is."""
93
+ defaults = {'compose': {'command': 'docker-compose'}}
94
+
95
+ assert merge_defaults(defaults, {}) == defaults
96
+
97
+
98
+ class TestMergeDefaultsAgainstTemplate:
99
+ """Tests merge_defaults against the real mantis.tpl defaults."""
100
+
101
+ def test_partial_compose_section_keeps_folder(self):
102
+ """The regression this fixes: compose.command alone used to lose compose.folder."""
103
+ config = {
104
+ 'compose': {'command': 'docker compose'},
105
+ 'connections': {'production': 'ssh://user@host:2222'},
106
+ }
107
+
108
+ merged = merge_defaults(load_template_config(), config)
109
+
110
+ assert merged['compose']['command'] == 'docker compose'
111
+ assert merged['compose']['folder'] == '<MANTIS>/../compose'
112
+
113
+ def test_every_template_section_survives_a_partial_override(self):
114
+ """Each section with more than one key must tolerate a partial declaration."""
115
+ template = load_template_config()
116
+ sections = {
117
+ key: value for key, value in template.items()
118
+ if isinstance(value, dict) and len(value) > 1
119
+ }
120
+
121
+ assert sections, 'template has no multi-key sections to check'
122
+
123
+ for section, defaults in sections.items():
124
+ first_key = next(iter(defaults))
125
+ merged = merge_defaults(template, {section: {first_key: defaults[first_key]}})
126
+
127
+ assert merged[section].keys() == defaults.keys(), \
128
+ f'partial override of "{section}" dropped keys'
@@ -1 +0,0 @@
1
- VERSION = '22.0.0'
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes