devservices 1.0.7__tar.gz → 1.0.8__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 (56) hide show
  1. {devservices-1.0.7 → devservices-1.0.8}/PKG-INFO +3 -1
  2. {devservices-1.0.7 → devservices-1.0.8}/README.md +1 -1
  3. {devservices-1.0.7 → devservices-1.0.8}/devservices/commands/status.py +18 -8
  4. {devservices-1.0.7 → devservices-1.0.8}/devservices/commands/up.py +2 -3
  5. {devservices-1.0.7 → devservices-1.0.8}/devservices/configs/service_config.py +1 -1
  6. {devservices-1.0.7 → devservices-1.0.8}/devservices/constants.py +1 -1
  7. {devservices-1.0.7 → devservices-1.0.8}/devservices/main.py +0 -9
  8. {devservices-1.0.7 → devservices-1.0.8}/devservices/utils/docker.py +1 -1
  9. {devservices-1.0.7 → devservices-1.0.8}/devservices/utils/file_lock.py +1 -1
  10. {devservices-1.0.7 → devservices-1.0.8}/devservices.egg-info/PKG-INFO +3 -1
  11. {devservices-1.0.7 → devservices-1.0.8}/devservices.egg-info/requires.txt +2 -0
  12. {devservices-1.0.7 → devservices-1.0.8}/pyproject.toml +3 -1
  13. {devservices-1.0.7 → devservices-1.0.8}/tests/commands/test_status.py +75 -1
  14. {devservices-1.0.7 → devservices-1.0.8}/tests/commands/test_up.py +1 -1
  15. {devservices-1.0.7 → devservices-1.0.8}/LICENSE.md +0 -0
  16. {devservices-1.0.7 → devservices-1.0.8}/devservices/__init__.py +0 -0
  17. {devservices-1.0.7 → devservices-1.0.8}/devservices/commands/__init__.py +0 -0
  18. {devservices-1.0.7 → devservices-1.0.8}/devservices/commands/down.py +0 -0
  19. {devservices-1.0.7 → devservices-1.0.8}/devservices/commands/list_dependencies.py +0 -0
  20. {devservices-1.0.7 → devservices-1.0.8}/devservices/commands/list_services.py +0 -0
  21. {devservices-1.0.7 → devservices-1.0.8}/devservices/commands/logs.py +0 -0
  22. {devservices-1.0.7 → devservices-1.0.8}/devservices/commands/purge.py +0 -0
  23. {devservices-1.0.7 → devservices-1.0.8}/devservices/commands/update.py +0 -0
  24. {devservices-1.0.7 → devservices-1.0.8}/devservices/exceptions.py +0 -0
  25. {devservices-1.0.7 → devservices-1.0.8}/devservices/utils/__init__.py +0 -0
  26. {devservices-1.0.7 → devservices-1.0.8}/devservices/utils/check_for_update.py +0 -0
  27. {devservices-1.0.7 → devservices-1.0.8}/devservices/utils/console.py +0 -0
  28. {devservices-1.0.7 → devservices-1.0.8}/devservices/utils/dependencies.py +0 -0
  29. {devservices-1.0.7 → devservices-1.0.8}/devservices/utils/devenv.py +0 -0
  30. {devservices-1.0.7 → devservices-1.0.8}/devservices/utils/docker_compose.py +0 -0
  31. {devservices-1.0.7 → devservices-1.0.8}/devservices/utils/install_binary.py +0 -0
  32. {devservices-1.0.7 → devservices-1.0.8}/devservices/utils/services.py +0 -0
  33. {devservices-1.0.7 → devservices-1.0.8}/devservices/utils/state.py +0 -0
  34. {devservices-1.0.7 → devservices-1.0.8}/devservices.egg-info/SOURCES.txt +0 -0
  35. {devservices-1.0.7 → devservices-1.0.8}/devservices.egg-info/dependency_links.txt +0 -0
  36. {devservices-1.0.7 → devservices-1.0.8}/devservices.egg-info/entry_points.txt +0 -0
  37. {devservices-1.0.7 → devservices-1.0.8}/devservices.egg-info/top_level.txt +0 -0
  38. {devservices-1.0.7 → devservices-1.0.8}/setup.cfg +0 -0
  39. {devservices-1.0.7 → devservices-1.0.8}/testing/__init__.py +0 -0
  40. {devservices-1.0.7 → devservices-1.0.8}/testing/utils.py +0 -0
  41. {devservices-1.0.7 → devservices-1.0.8}/tests/__init__.py +0 -0
  42. {devservices-1.0.7 → devservices-1.0.8}/tests/commands/test_down.py +0 -0
  43. {devservices-1.0.7 → devservices-1.0.8}/tests/commands/test_list_dependencies.py +0 -0
  44. {devservices-1.0.7 → devservices-1.0.8}/tests/commands/test_list_services.py +0 -0
  45. {devservices-1.0.7 → devservices-1.0.8}/tests/commands/test_logs.py +0 -0
  46. {devservices-1.0.7 → devservices-1.0.8}/tests/commands/test_purge.py +0 -0
  47. {devservices-1.0.7 → devservices-1.0.8}/tests/commands/test_update.py +0 -0
  48. {devservices-1.0.7 → devservices-1.0.8}/tests/configs/test_service_config.py +0 -0
  49. {devservices-1.0.7 → devservices-1.0.8}/tests/conftest.py +0 -0
  50. {devservices-1.0.7 → devservices-1.0.8}/tests/utils/test_check_for_update.py +0 -0
  51. {devservices-1.0.7 → devservices-1.0.8}/tests/utils/test_dependencies.py +0 -0
  52. {devservices-1.0.7 → devservices-1.0.8}/tests/utils/test_docker.py +0 -0
  53. {devservices-1.0.7 → devservices-1.0.8}/tests/utils/test_docker_compose.py +0 -0
  54. {devservices-1.0.7 → devservices-1.0.8}/tests/utils/test_install_binary.py +0 -0
  55. {devservices-1.0.7 → devservices-1.0.8}/tests/utils/test_services.py +0 -0
  56. {devservices-1.0.7 → devservices-1.0.8}/tests/utils/test_state.py +0 -0
@@ -1,10 +1,12 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: devservices
3
- Version: 1.0.7
3
+ Version: 1.0.8
4
4
  Requires-Python: >=3.10
5
5
  License-File: LICENSE.md
6
6
  Requires-Dist: pyyaml
7
7
  Requires-Dist: sentry-devenv
8
+ Requires-Dist: sentry-sdk
9
+ Requires-Dist: packaging
8
10
  Provides-Extra: dev
9
11
  Requires-Dist: black; extra == "dev"
10
12
  Requires-Dist: mypy; extra == "dev"
@@ -11,7 +11,7 @@ A standalone cli tool used to manage dependencies for services. It simplifies th
11
11
  The recommended way to install devservices is through a virtualenv in the requirements.txt.
12
12
 
13
13
  ```
14
- devservices==1.0.7
14
+ devservices==1.0.8
15
15
  ```
16
16
 
17
17
 
@@ -7,6 +7,7 @@ import subprocess
7
7
  from argparse import _SubParsersAction
8
8
  from argparse import ArgumentParser
9
9
  from argparse import Namespace
10
+ from collections import namedtuple
10
11
 
11
12
  from sentry_sdk import capture_exception
12
13
 
@@ -30,6 +31,9 @@ from devservices.utils.services import Service
30
31
  LINE_LENGTH = 40
31
32
 
32
33
 
34
+ ServiceStatus = namedtuple("ServiceStatus", ["name", "formatted_output"])
35
+
36
+
33
37
  def add_parser(subparsers: _SubParsersAction[ArgumentParser]) -> None:
34
38
  parser = subparsers.add_parser("status", help="View status of a service")
35
39
  parser.add_argument(
@@ -41,12 +45,12 @@ def add_parser(subparsers: _SubParsersAction[ArgumentParser]) -> None:
41
45
  parser.set_defaults(func=status)
42
46
 
43
47
 
44
- def format_status_output(status_json: str) -> str:
48
+ def format_status_output(service_status_json: str) -> list[ServiceStatus]:
45
49
  # Docker compose ps is line delimited json, so this constructs this into an array we can use
46
- service_statuses = status_json.split("\n")[:-1]
47
- output = []
48
- output.append("-" * LINE_LENGTH)
50
+ service_statuses = service_status_json.split("\n")[:-1]
51
+ outputs = []
49
52
  for service_status in service_statuses:
53
+ output = []
50
54
  service = json.loads(service_status)
51
55
  name = service["Service"]
52
56
  state = service["State"]
@@ -71,8 +75,9 @@ def format_status_output(status_json: str) -> str:
71
75
  output.append("No ports exposed")
72
76
 
73
77
  output.append("") # Empty line for readability
78
+ outputs.append(ServiceStatus(name=name, formatted_output="\n".join(output)))
74
79
 
75
- return "\n".join(output)
80
+ return outputs
76
81
 
77
82
 
78
83
  def status(args: Namespace) -> None:
@@ -115,10 +120,15 @@ def status(args: Namespace) -> None:
115
120
  console.warning(f"{service.name} is not running")
116
121
  return
117
122
  output = f"Service: {service.name}\n\n"
123
+ output += "=" * LINE_LENGTH + "\n"
124
+ formatted_status_outputs = []
118
125
  for status_json in status_json_results:
119
- output += format_status_output(status_json.stdout)
120
- output += "=" * LINE_LENGTH
121
- console.info(output + "\n")
126
+ formatted_status_outputs.extend(format_status_output(status_json.stdout))
127
+ formatted_status_outputs.sort(key=lambda x: x.name)
128
+ for formatted_status_output in formatted_status_outputs:
129
+ output += formatted_status_output[1]
130
+ output += "-" * LINE_LENGTH + "\n"
131
+ console.info(output)
122
132
 
123
133
 
124
134
  def _status(
@@ -107,7 +107,6 @@ def up(args: Namespace) -> None:
107
107
  def _bring_up_dependency(
108
108
  cmd: DockerComposeCommand, current_env: dict[str, str], status: Status
109
109
  ) -> subprocess.CompletedProcess[str]:
110
- # TODO: Get rid of these constants, we need a smarter way to determine the containers being brought up
111
110
  for dependency in cmd.services:
112
111
  status.info(f"Starting {dependency}")
113
112
  return run_cmd(cmd.full_command, current_env)
@@ -132,7 +131,6 @@ def _up(
132
131
  current_env[
133
132
  DEVSERVICES_DEPENDENCIES_CACHE_DIR_KEY
134
133
  ] = relative_local_dependency_directory
135
- options = ["-d", "--pull", "always"]
136
134
  dependency_graph = construct_dependency_graph(service, modes=modes)
137
135
  starting_order = dependency_graph.get_starting_order()
138
136
  sorted_remote_dependencies = sorted(
@@ -143,7 +141,7 @@ def _up(
143
141
  remote_dependencies=sorted_remote_dependencies,
144
142
  current_env=current_env,
145
143
  command="up",
146
- options=options,
144
+ options=["-d", "--pull", "always"],
147
145
  service_config_file_path=service_config_file_path,
148
146
  mode_dependencies=mode_dependencies,
149
147
  )
@@ -180,4 +178,5 @@ def _create_devservices_network() -> None:
180
178
  ["docker", "network", "create", "devservices"],
181
179
  stdout=subprocess.DEVNULL,
182
180
  stderr=subprocess.DEVNULL,
181
+ check=True,
183
182
  )
@@ -68,7 +68,7 @@ def load_service_config_from_file(repo_path: str) -> ServiceConfig:
68
68
  config_path = os.path.join(repo_path, DEVSERVICES_DIR_NAME, CONFIG_FILE_NAME)
69
69
  if not os.path.exists(config_path):
70
70
  raise ConfigNotFoundError(f"Config file not found in directory: {config_path}")
71
- with open(config_path, "r") as stream:
71
+ with open(config_path, "r", encoding="utf-8") as stream:
72
72
  try:
73
73
  config = yaml.safe_load(stream)
74
74
  except yaml.YAMLError as yml_error:
@@ -37,5 +37,5 @@ DEVSERVICES_LATEST_VERSION_CACHE_FILE = os.path.join(
37
37
  DEVSERVICES_CACHE_DIR, "latest_version.txt"
38
38
  )
39
39
  DEVSERVICES_LATEST_VERSION_CACHE_TTL = timedelta(minutes=15)
40
- HEALTHCHECK_TIMEOUT = 30
40
+ HEALTHCHECK_TIMEOUT = 45
41
41
  HEALTHCHECK_INTERVAL = 5
@@ -26,7 +26,6 @@ from devservices.commands import update
26
26
  from devservices.constants import LOGGER_NAME
27
27
  from devservices.exceptions import DockerComposeInstallationError
28
28
  from devservices.exceptions import DockerDaemonNotRunningError
29
- from devservices.utils.check_for_update import check_for_update
30
29
  from devservices.utils.console import Console
31
30
  from devservices.utils.docker_compose import check_docker_compose_version
32
31
 
@@ -102,14 +101,6 @@ def main() -> None:
102
101
  else:
103
102
  parser.print_help()
104
103
 
105
- if args.command != "update" and os.environ.get("CI") != "true":
106
- newest_version = check_for_update()
107
- if newest_version != current_version:
108
- console.warning(
109
- f"WARNING: A new version of devservices is available: {newest_version}"
110
- )
111
- console.warning('To update, run: "devservices update"')
112
-
113
104
 
114
105
  if __name__ == "__main__":
115
106
  main()
@@ -66,7 +66,7 @@ def wait_for_healthy(container_name: str, status: Status) -> None:
66
66
 
67
67
  if result == "healthy":
68
68
  return
69
- elif result == "unknown":
69
+ if result == "unknown":
70
70
  status.warning(
71
71
  f"WARNING: Container {container_name} does not have a healthcheck"
72
72
  )
@@ -7,7 +7,7 @@ from collections.abc import Generator
7
7
 
8
8
  @contextlib.contextmanager
9
9
  def lock(path: str) -> Generator[None, None, None]:
10
- with open(path, mode="a+") as f:
10
+ with open(path, mode="a+", encoding="utf-8") as f:
11
11
  with _locked(f.fileno()):
12
12
  yield
13
13
 
@@ -1,10 +1,12 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: devservices
3
- Version: 1.0.7
3
+ Version: 1.0.8
4
4
  Requires-Python: >=3.10
5
5
  License-File: LICENSE.md
6
6
  Requires-Dist: pyyaml
7
7
  Requires-Dist: sentry-devenv
8
+ Requires-Dist: sentry-sdk
9
+ Requires-Dist: packaging
8
10
  Provides-Extra: dev
9
11
  Requires-Dist: black; extra == "dev"
10
12
  Requires-Dist: mypy; extra == "dev"
@@ -1,5 +1,7 @@
1
1
  pyyaml
2
2
  sentry-devenv
3
+ sentry-sdk
4
+ packaging
3
5
 
4
6
  [dev]
5
7
  black
@@ -4,12 +4,14 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "devservices"
7
- version = "1.0.7"
7
+ version = "1.0.8"
8
8
  # 3.10 is just for internal pypi compat
9
9
  requires-python = ">=3.10"
10
10
  dependencies = [
11
11
  "pyyaml",
12
12
  "sentry-devenv",
13
+ "sentry-sdk",
14
+ "packaging",
13
15
  ]
14
16
 
15
17
  [project.optional-dependencies]
@@ -8,6 +8,7 @@ from unittest import mock
8
8
  import pytest
9
9
 
10
10
  from devservices.commands.status import status
11
+ from devservices.configs.service_config import Dependency
11
12
  from devservices.configs.service_config import ServiceConfig
12
13
  from devservices.exceptions import DependencyError
13
14
  from devservices.exceptions import ServiceNotFoundError
@@ -156,7 +157,7 @@ def test_status_service_running(
156
157
  assert (
157
158
  """Service: test-service
158
159
 
159
- ----------------------------------------
160
+ ========================================
160
161
  test-service
161
162
  Container: test-container
162
163
  Status: running
@@ -164,7 +165,80 @@ Health: healthy
164
165
  Uptime: 2 days ago
165
166
  Ports:
166
167
  http://localhost:8080:8080 -> 8080/tcp
168
+ ----------------------------------------
169
+
170
+ """
171
+ == captured.out
172
+ )
173
+
174
+
175
+ @mock.patch("devservices.commands.status._status")
176
+ @mock.patch("devservices.commands.status.find_matching_service")
177
+ @mock.patch("devservices.commands.status.install_and_verify_dependencies")
178
+ def test_status_services_running_sorted_order(
179
+ mock_install_and_verify_dependencies: mock.Mock,
180
+ mock_find_matching_service: mock.Mock,
181
+ mock_status: mock.Mock,
182
+ capsys: pytest.CaptureFixture[str],
183
+ tmp_path: Path,
184
+ ) -> None:
185
+ args = Namespace(service_name="test-service")
186
+ service = Service(
187
+ name="test-service",
188
+ repo_path=str(tmp_path),
189
+ config=ServiceConfig(
190
+ version=0.1,
191
+ service_name="test-service",
192
+ dependencies={
193
+ "test-dependency": Dependency(
194
+ description="Test dependency",
195
+ )
196
+ },
197
+ modes={"default": []},
198
+ ),
199
+ )
200
+ mock_find_matching_service.return_value = service
201
+ mock_install_and_verify_dependencies.return_value = set()
202
+ mock_status.return_value = [
203
+ subprocess.CompletedProcess(
204
+ args=[],
205
+ returncode=0,
206
+ stdout='{"Service": "test-service", "State": "running", "Name": "test-container", "Health": "healthy", "RunningFor": "2 days ago", "Publishers": [{"URL": "http://localhost:8080", "PublishedPort": 8080, "TargetPort": 8080, "Protocol": "tcp"}]}\n',
207
+ ),
208
+ subprocess.CompletedProcess(
209
+ args=[],
210
+ returncode=0,
211
+ stdout='{"Service": "test-dependency", "State": "running", "Name": "test-dependency-container", "Health": "healthy", "RunningFor": "2 days ago", "Publishers": [{"URL": "http://localhost:8081", "PublishedPort": 8081, "TargetPort": 8081, "Protocol": "tcp"}]}\n',
212
+ ),
213
+ ]
214
+
215
+ status(args)
216
+
217
+ mock_find_matching_service.assert_called_once_with("test-service")
218
+ mock_install_and_verify_dependencies.assert_called_once_with(service)
219
+ mock_status.assert_called_once_with(service, set(), [])
220
+
221
+ captured = capsys.readouterr()
222
+ assert (
223
+ """Service: test-service
224
+
167
225
  ========================================
226
+ test-dependency
227
+ Container: test-dependency-container
228
+ Status: running
229
+ Health: healthy
230
+ Uptime: 2 days ago
231
+ Ports:
232
+ http://localhost:8081:8081 -> 8081/tcp
233
+ ----------------------------------------
234
+ test-service
235
+ Container: test-container
236
+ Status: running
237
+ Health: healthy
238
+ Uptime: 2 days ago
239
+ Ports:
240
+ http://localhost:8080:8080 -> 8080/tcp
241
+ ----------------------------------------
168
242
 
169
243
  """
170
244
  == captured.out
@@ -457,7 +457,7 @@ def test_up_docker_compose_container_healthcheck_failed(
457
457
  assert "Starting clickhouse" in captured.out.strip()
458
458
  assert "Starting redis" in captured.out.strip()
459
459
  assert (
460
- "Container container1 did not become healthy within 30 seconds."
460
+ "Container container1 did not become healthy within 45 seconds."
461
461
  in captured.out.strip()
462
462
  )
463
463
 
File without changes
File without changes