atlas-init 0.3.1__py3-none-any.whl → 0.3.2__py3-none-any.whl

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.
atlas_init/__init__.py CHANGED
@@ -1,6 +1,6 @@
1
1
  from pathlib import Path
2
2
 
3
- VERSION = "0.3.1"
3
+ VERSION = "0.3.2"
4
4
 
5
5
 
6
6
  def running_in_repo() -> bool:
@@ -98,12 +98,12 @@ class SDKRoundtrip(Entity):
98
98
  @property
99
99
  def version(self) -> str:
100
100
  content_type = self.response.headers.get("Content-Type", "v1")
101
- try:
101
+ content_type_req = self.request.headers.get("Accept", "v1")
102
+ with suppress(ValueError):
102
103
  return extract_version(content_type)
103
- except ValueError:
104
- logger.warning(f"failed to extract version from response header ({content_type}), trying request")
105
- content_type = self.request.headers.get("Accept", "v1")
106
- return extract_version(content_type)
104
+ with suppress(ValueError):
105
+ return extract_version(content_type_req)
106
+ raise ValueError(f"Could not extract version from req/resp: {content_type} or {content_type_req}")
107
107
 
108
108
  @model_validator(mode="after")
109
109
  def ensure_match(self) -> Self:
@@ -159,7 +159,7 @@ def parse_http_requests(logs: str) -> list[SDKRoundtrip]:
159
159
  Can say that expected payload is either a list or a dict and if it ends with an identifier it is higher chance for a dict
160
160
  """
161
161
  test_name = parse_test_name(logs)
162
- logger.info(f"Finding http requests for test name: {test_name}")
162
+ logger.info(f"Finding http requests for test name: '{test_name}'")
163
163
  requests, responses = parse_raw_req_responses(logs)
164
164
  tf_step_starts = [i for i, line in enumerate(logs.splitlines()) if MARKER_START_STEP in line]
165
165
  used_responses: set[int] = set()
@@ -31,7 +31,7 @@ class RequestInfo(Entity):
31
31
 
32
32
  @property
33
33
  def id(self):
34
- return "__".join((self.method, self.path, self.version, self.text)) # noqa: FLY002
34
+ return "__".join(part for part in (self.method, self.path, self.version, self.text) if part)
35
35
 
36
36
 
37
37
  class StepRequests(Entity):
@@ -143,9 +143,9 @@ class MockRequestData(Entity):
143
143
  def replace_text_variables(self):
144
144
  for step in self.steps:
145
145
  for request in step.all_requests:
146
- request.text = normalize_text(request.text, self.variables)
146
+ request.text = normalize_text(request.text, self.variables, expect_json=True)
147
147
  for response in request.responses:
148
- response.text = normalize_text(response.text, self.variables)
148
+ response.text = normalize_text(response.text, self.variables, expect_json=True)
149
149
 
150
150
  def prune_duplicate_responses(self):
151
151
  for step in self.steps:
@@ -202,10 +202,10 @@ def find_normalized_path(path: str, api_spec_paths: list[ApiSpecPath]) -> ApiSpe
202
202
  raise ValueError(f"Could not find path: {path}")
203
203
 
204
204
 
205
- def normalize_text(text: str, variables: dict[str, str]) -> str:
205
+ def normalize_text(text: str, variables: dict[str, str], *, expect_json: bool = False) -> str:
206
206
  for var, value in variables.items():
207
207
  text = text.replace(value, f"{{{var}}}")
208
- if not text:
208
+ if not text or not expect_json:
209
209
  return text
210
210
  try:
211
211
  parsed_text = json.loads(text)
@@ -260,8 +260,8 @@ def create_mock_data(
260
260
  for modifier in modifiers:
261
261
  if modifier.match(rt, normalized_path):
262
262
  modifier.modification(rt)
263
- normalized_text = normalize_text(rt.request.text, mock_data.variables)
264
- normalized_response_text = normalize_text(rt.response.text, mock_data.variables)
263
+ normalized_text = normalize_text(rt.request.text, mock_data.variables, expect_json=True)
264
+ normalized_response_text = normalize_text(rt.response.text, mock_data.variables, expect_json=True)
265
265
  mock_data.add_roundtrip(rt, normalized_path, normalized_text, normalized_response_text, is_diff(rt))
266
266
  mock_data.replace_text_variables()
267
267
  if prune_duplicates:
@@ -1,11 +1,14 @@
1
1
  import json
2
2
  import logging
3
3
  import time
4
+ from collections.abc import Callable
5
+ from io import StringIO
4
6
  from pathlib import Path
5
7
  from typing import Self
6
8
 
7
9
  import typer
8
- from model_lib import Entity, dump
10
+ import yaml
11
+ from model_lib import Entity
9
12
  from pydantic import Field, model_validator
10
13
  from zero_3rdparty import file_utils
11
14
 
@@ -15,8 +18,16 @@ from atlas_init.cli_tf.debug_logs import (
15
18
  parse_http_requests,
16
19
  parse_test_name,
17
20
  )
18
- from atlas_init.cli_tf.debug_logs_test_data import create_mock_data, default_is_diff
19
- from atlas_init.repos.go_sdk import api_spec_path_transformed, download_admin_api, parse_api_spec_paths
21
+ from atlas_init.cli_tf.debug_logs_test_data import (
22
+ RTModifier,
23
+ create_mock_data,
24
+ default_is_diff,
25
+ )
26
+ from atlas_init.repos.go_sdk import (
27
+ api_spec_path_transformed,
28
+ download_admin_api,
29
+ parse_api_spec_paths,
30
+ )
20
31
  from atlas_init.settings.path import DEFAULT_DOWNLOADS_DIR
21
32
 
22
33
  logger = logging.getLogger(__name__)
@@ -28,6 +39,8 @@ class MockTFLog(Entity):
28
39
  admin_api_path: Path
29
40
  diff_skip_suffixes: list[str] = Field(default_factory=list)
30
41
  keep_duplicates: bool = False
42
+ modifiers: list[RTModifier] = Field(default_factory=list)
43
+ log_diff_roundtrips: bool = False
31
44
 
32
45
  @model_validator(mode="after")
33
46
  def ensure_paths_exist(self) -> Self:
@@ -44,22 +57,40 @@ class MockTFLog(Entity):
44
57
  return default_is_diff(rt) and not any(rt.request.path.endswith(suffix) for suffix in self.diff_skip_suffixes)
45
58
 
46
59
 
47
- def mock_tf_log(req: MockTFLog) -> None:
60
+ def mock_tf_log(req: MockTFLog) -> Path:
48
61
  log_file_text = req.log_path.read_text()
49
62
  test_name = parse_test_name(log_file_text)
50
63
  roundtrips = parse_http_requests(log_file_text)
64
+ logger.info(f"Found #{len(roundtrips)} roundtrips")
65
+ if req.log_diff_roundtrips:
66
+ log_diff_roundtrips(roundtrips, req.differ)
51
67
  api_spec_paths = parse_api_spec_paths(req.admin_api_path)
52
68
  data = create_mock_data(
53
69
  roundtrips,
54
70
  api_spec_paths,
55
71
  is_diff=req.differ,
56
72
  prune_duplicates=not req.keep_duplicates,
73
+ modifiers=req.modifiers,
57
74
  )
58
75
  # avoid anchors
59
- data_yaml = dump(json.loads(dump(data, "json")), "yaml")
76
+ data_json = data.model_dump_json(exclude_none=True)
77
+ data_parsed = json.loads(data_json)
78
+ s = StringIO()
79
+ yaml.safe_dump(
80
+ data_parsed,
81
+ s,
82
+ default_flow_style=False,
83
+ width=100_000,
84
+ allow_unicode=True,
85
+ sort_keys=False,
86
+ )
87
+ data_yaml = s.getvalue()
88
+ test_name = test_name.replace("TestAcc", "TestMock")
60
89
  output_path = req.output_dir / f"{test_name}.yaml"
90
+ logger.info(f"Variables found {data.variables}")
61
91
  logger.info(f"Writing to {output_path}")
62
92
  file_utils.ensure_parents_write_text(output_path, data_yaml)
93
+ return output_path
63
94
 
64
95
 
65
96
  def mock_tf_log_cmd(
@@ -68,7 +99,7 @@ def mock_tf_log_cmd(
68
99
  "",
69
100
  "-o",
70
101
  "--output-testdir",
71
- help="the path to the output test directory, for example: internal/service/advancedclustertpf/testdata/, uses cwd/testdata by default",
102
+ help="the path to the output test directory, for example: internal/service/advancedclustertpf/testdata/, uses $(cwd)/testdata by default",
72
103
  ),
73
104
  sdk_repo_path_str: str = option_sdk_repo_path,
74
105
  sdk_branch: str = typer.Option("main", "-b", "--branch", help="the branch for downloading openapi spec"),
@@ -77,6 +108,9 @@ def mock_tf_log_cmd(
77
108
  ),
78
109
  diff_skip_suffixes: list[str] = typer.Option(..., "-s", "--skip-suffixes", default_factory=list),
79
110
  keep_duplicates: bool = typer.Option(False, "-keep", "--keep-duplicates", help="keep duplicate requests"),
111
+ log_diff_roundtrips: bool = typer.Option(
112
+ False, "-l", "--log-diff-roundtrips", help="print out the roundtrips used in diffs"
113
+ ),
80
114
  ):
81
115
  cwd = Path.cwd()
82
116
  default_testdir = cwd / "testdata"
@@ -87,6 +121,7 @@ def mock_tf_log_cmd(
87
121
  admin_api_path=resolved_admin_api_path,
88
122
  diff_skip_suffixes=diff_skip_suffixes,
89
123
  keep_duplicates=keep_duplicates,
124
+ log_diff_roundtrips=log_diff_roundtrips,
90
125
  )
91
126
  mock_tf_log(event_in)
92
127
 
@@ -116,3 +151,20 @@ def resolve_admin_api_path(sdk_repo_path_str: str, sdk_branch: str, admin_api_pa
116
151
  assert resolved_admin_api_path.exists(), f"unable to resolve admin_api_path={resolved_admin_api_path}"
117
152
  assert resolved_admin_api_path.is_file(), f"not a file admin_api_path={resolved_admin_api_path}"
118
153
  return resolved_admin_api_path
154
+
155
+
156
+ def log_diff_roundtrips(roundtrips: list[SDKRoundtrip], differ: Callable[[SDKRoundtrip], bool] | None = None):
157
+ differ = differ or default_is_diff
158
+ diff_count = 0
159
+ step_nr = 0
160
+ for rt in roundtrips:
161
+ if not differ(rt):
162
+ continue
163
+ if rt.step_number != step_nr:
164
+ logger.info(f"{'-' * 80}\nStep {rt.step_number}")
165
+ step_nr = rt.step_number
166
+ diff_count += 1
167
+ logger.info(
168
+ f"\n{rt.request.method} {rt.request.path}\n{rt.request.text}\n{rt.response.status}-{rt.response.status_text}\n{rt.response.text}"
169
+ )
170
+ logger.info(f"Diffable requests: {diff_count}")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: atlas-init
3
- Version: 0.3.1
3
+ Version: 0.3.2
4
4
  Project-URL: Documentation, https://github.com/EspenAlbert/atlas-init#readme
5
5
  Project-URL: Issues, https://github.com/EspenAlbert/atlas-init/issues
6
6
  Project-URL: Source, https://github.com/EspenAlbert/atlas-init
@@ -1,4 +1,4 @@
1
- atlas_init/__init__.py,sha256=Mja1wzBho0kdXUYEG2JEXu72cDv6N_Uf0xLaUN7aQsA,372
1
+ atlas_init/__init__.py,sha256=5-YoPkhK1DsNmcQTFT5lp8zxJ26l6WRY5tMcoGBug6o,372
2
2
  atlas_init/__main__.py,sha256=dY1dWWvwxRZMmnOFla6RSfti-hMeLeKdoXP7SVYqMUc,52
3
3
  atlas_init/atlas_init.yaml,sha256=GMyJVhKKRc7WzEu7fafmWgeTsDaExTLv7QvXOmE_Brg,1907
4
4
  atlas_init/cli.py,sha256=IiOEC_Jry6vrSDH3_OvsU50F-_3iVIS4tV6-R7659fY,9642
@@ -23,13 +23,13 @@ atlas_init/cli_root/trigger.py,sha256=oEgqb_l25tyYgUaFHEuChcOCJA7k3mnRa4D-Myz-Ig
23
23
  atlas_init/cli_tf/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
24
24
  atlas_init/cli_tf/app.py,sha256=0Y5c-Pc9ibOz6kXvFlL-yhH_fx1nHLgBgK9OAVqjX9s,11390
25
25
  atlas_init/cli_tf/changelog.py,sha256=biWYKf1pZvXZ-jEgcZ5q9sY7nTGrL2PuI0h9mCILf_g,3181
26
- atlas_init/cli_tf/debug_logs.py,sha256=lnB5BpcEooVzGd2RLxbwAVQs0ZYXzRKy5sHa0hftHI8,8799
27
- atlas_init/cli_tf/debug_logs_test_data.py,sha256=bv4gqhHSNEnQqIijrcjvEUA0M6S-aeo73V4mji0pKCM,9435
26
+ atlas_init/cli_tf/debug_logs.py,sha256=r9kRMzgZGP7SB0eK7hhbxETfI28yyHFtSpvSFe9NGC4,8839
27
+ atlas_init/cli_tf/debug_logs_test_data.py,sha256=G4pnuWJ7PAQd3NXRKAtwAPC6Ne-PgpzaTZHQ9waqxZI,9565
28
28
  atlas_init/cli_tf/github_logs.py,sha256=VD7qhlXNuG21eTuJ5VI7rsflp5WHSodfngkRVgQlumw,8114
29
29
  atlas_init/cli_tf/go_test_run.py,sha256=ZoQSvIasmWauFxZJrWL0ObFX-P0k-D3c_ep3OnPY4zs,5842
30
30
  atlas_init/cli_tf/go_test_run_format.py,sha256=OUd6QPHDeTzbwVuh6MhP-xXgjOOGP9W_sCLJ8KylBTs,1201
31
31
  atlas_init/cli_tf/go_test_summary.py,sha256=agr4SITgxchjgOzRpScoTUk-iG38QDLkpnsMtTW9GTY,5382
32
- atlas_init/cli_tf/mock_tf_log.py,sha256=c0geBR74UkHiyElnV0R_yTuXUgP4F_H53rbGj6D99yc,4958
32
+ atlas_init/cli_tf/mock_tf_log.py,sha256=u_d6c-lVo3eDddpW3V5r3FJfvQvoyrC8NnOGS1bYpR8,6572
33
33
  atlas_init/cli_tf/schema.py,sha256=iwvb4wD2Wba0MMu7ooTNAIi1jHbpLiXGPOT51_o_YW8,12431
34
34
  atlas_init/cli_tf/schema_go_parser.py,sha256=PiRfFFVnkhltxcGFfOCgH53wwzIEynw2BXmSfaINLL8,8294
35
35
  atlas_init/cli_tf/schema_inspection.py,sha256=ujLvGfg3baByND4nRD0drZoI45STxo3VfYvim-PfVOc,1764
@@ -86,7 +86,7 @@ atlas_init/tf/modules/vpc_peering/vpc_peering.tf,sha256=hJ3KJdGbLpOQednUpVuiJ0Cq
86
86
  atlas_init/tf/modules/vpc_privatelink/atlas-privatelink.tf,sha256=FloaaX1MNDvoMZxBnEopeLKyfIlq6kaX2dmx8WWlXNU,1298
87
87
  atlas_init/tf/modules/vpc_privatelink/variables.tf,sha256=gktHCDYD4rz6CEpLg5aiXcFbugw4L5S2Fqc52QYdJyc,255
88
88
  atlas_init/tf/modules/vpc_privatelink/versions.tf,sha256=G0u5V_Hvvrkux_tqfOY05pA-GzSp_qILpfx1dZaTGDc,237
89
- atlas_init-0.3.1.dist-info/METADATA,sha256=vb-qsxAC6t4yI4ec_x1L-nYaGT2sGSt0LCIRHBPSgl8,5650
90
- atlas_init-0.3.1.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
91
- atlas_init-0.3.1.dist-info/entry_points.txt,sha256=oSNFIEAS9nUZyyZ8Fc-0F0U5j-NErygy01LpJVSHapQ,57
92
- atlas_init-0.3.1.dist-info/RECORD,,
89
+ atlas_init-0.3.2.dist-info/METADATA,sha256=foETVpu_FKi1rkGOJB158pmAftoNYFiG9VNr1kPBPvA,5650
90
+ atlas_init-0.3.2.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
91
+ atlas_init-0.3.2.dist-info/entry_points.txt,sha256=oSNFIEAS9nUZyyZ8Fc-0F0U5j-NErygy01LpJVSHapQ,57
92
+ atlas_init-0.3.2.dist-info/RECORD,,