atom-tools 0.8.2__tar.gz → 0.8.4__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.
- {atom_tools-0.8.2/atom_tools.egg-info → atom_tools-0.8.4}/PKG-INFO +1 -1
- {atom_tools-0.8.2 → atom_tools-0.8.4}/atom_tools/__init__.py +1 -1
- {atom_tools-0.8.2 → atom_tools-0.8.4}/atom_tools/lib/converter.py +100 -1
- {atom_tools-0.8.2 → atom_tools-0.8.4/atom_tools.egg-info}/PKG-INFO +1 -1
- {atom_tools-0.8.2 → atom_tools-0.8.4}/pyproject.toml +1 -1
- {atom_tools-0.8.2 → atom_tools-0.8.4}/test/test_converter.py +42 -6
- {atom_tools-0.8.2 → atom_tools-0.8.4}/LICENSE +0 -0
- {atom_tools-0.8.2 → atom_tools-0.8.4}/MANIFEST.in +0 -0
- {atom_tools-0.8.2 → atom_tools-0.8.4}/README.md +0 -0
- {atom_tools-0.8.2 → atom_tools-0.8.4}/atom_tools/cli/__init__.py +0 -0
- {atom_tools-0.8.2 → atom_tools-0.8.4}/atom_tools/cli/application.py +0 -0
- {atom_tools-0.8.2 → atom_tools-0.8.4}/atom_tools/cli/command_loader.py +0 -0
- {atom_tools-0.8.2 → atom_tools-0.8.4}/atom_tools/cli/commands/__init__.py +0 -0
- {atom_tools-0.8.2 → atom_tools-0.8.4}/atom_tools/cli/commands/check_reachable.py +0 -0
- {atom_tools-0.8.2 → atom_tools-0.8.4}/atom_tools/cli/commands/command.py +0 -0
- {atom_tools-0.8.2 → atom_tools-0.8.4}/atom_tools/cli/commands/convert.py +0 -0
- {atom_tools-0.8.2 → atom_tools-0.8.4}/atom_tools/cli/commands/filter.py +0 -0
- {atom_tools-0.8.2 → atom_tools-0.8.4}/atom_tools/cli/commands/query_endpoints.py +0 -0
- {atom_tools-0.8.2 → atom_tools-0.8.4}/atom_tools/cli/commands/validate_lines.py +0 -0
- {atom_tools-0.8.2 → atom_tools-0.8.4}/atom_tools/cli/logging_config.py +0 -0
- {atom_tools-0.8.2 → atom_tools-0.8.4}/atom_tools/lib/__init__.py +0 -0
- {atom_tools-0.8.2 → atom_tools-0.8.4}/atom_tools/lib/filtering.py +0 -0
- {atom_tools-0.8.2 → atom_tools-0.8.4}/atom_tools/lib/regex_utils.py +0 -0
- {atom_tools-0.8.2 → atom_tools-0.8.4}/atom_tools/lib/ruby_converter.py +0 -0
- {atom_tools-0.8.2 → atom_tools-0.8.4}/atom_tools/lib/ruby_semantics.py +0 -0
- {atom_tools-0.8.2 → atom_tools-0.8.4}/atom_tools/lib/scala_converter.py +0 -0
- {atom_tools-0.8.2 → atom_tools-0.8.4}/atom_tools/lib/slices.py +0 -0
- {atom_tools-0.8.2 → atom_tools-0.8.4}/atom_tools/lib/utils.py +0 -0
- {atom_tools-0.8.2 → atom_tools-0.8.4}/atom_tools/lib/validator.py +0 -0
- {atom_tools-0.8.2 → atom_tools-0.8.4}/atom_tools.egg-info/SOURCES.txt +0 -0
- {atom_tools-0.8.2 → atom_tools-0.8.4}/atom_tools.egg-info/dependency_links.txt +0 -0
- {atom_tools-0.8.2 → atom_tools-0.8.4}/atom_tools.egg-info/entry_points.txt +0 -0
- {atom_tools-0.8.2 → atom_tools-0.8.4}/atom_tools.egg-info/requires.txt +0 -0
- {atom_tools-0.8.2 → atom_tools-0.8.4}/atom_tools.egg-info/top_level.txt +0 -0
- {atom_tools-0.8.2 → atom_tools-0.8.4}/setup.cfg +0 -0
- {atom_tools-0.8.2 → atom_tools-0.8.4}/test/test_filtering.py +0 -0
- {atom_tools-0.8.2 → atom_tools-0.8.4}/test/test_ruby_semantics.py +0 -0
- {atom_tools-0.8.2 → atom_tools-0.8.4}/test/test_slices.py +0 -0
- {atom_tools-0.8.2 → atom_tools-0.8.4}/test/test_utils.py +0 -0
|
@@ -71,7 +71,11 @@ class OpenAPI:
|
|
|
71
71
|
self.target_line_nums = self._identify_target_line_nums(methods)
|
|
72
72
|
self.file_endpoint_map = self.create_file_to_method_dict(methods)
|
|
73
73
|
methods = self._process_calls(methods)
|
|
74
|
-
|
|
74
|
+
paths = self.populate_endpoints(methods)
|
|
75
|
+
udt_methods = self._extract_methods_from_udt()
|
|
76
|
+
if udt_methods:
|
|
77
|
+
paths = merge_path_objects(paths, udt_methods)
|
|
78
|
+
return paths
|
|
75
79
|
|
|
76
80
|
def create_file_to_method_dict(self, method_map: Dict[str, Any]) -> Dict[str, List]:
|
|
77
81
|
"""
|
|
@@ -149,8 +153,16 @@ class OpenAPI:
|
|
|
149
153
|
dict: A new method map containing endpoints.
|
|
150
154
|
"""
|
|
151
155
|
new_method_map: Dict = {'file_names': {}}
|
|
156
|
+
class_prefixes = self._get_java_class_prefixes()
|
|
152
157
|
for file_name, resolved_methods in method_map.items():
|
|
153
158
|
if new_resolved := self._process_resolved_methods(resolved_methods):
|
|
159
|
+
prefix = class_prefixes.get(file_name, '')
|
|
160
|
+
if prefix:
|
|
161
|
+
for method_key in new_resolved:
|
|
162
|
+
new_resolved[method_key]['endpoints'] = [
|
|
163
|
+
prefix.rstrip('/') + ep
|
|
164
|
+
for ep in new_resolved[method_key]['endpoints']
|
|
165
|
+
]
|
|
154
166
|
new_method_map['file_names'][file_name] = {'resolved_methods': new_resolved}
|
|
155
167
|
return new_method_map
|
|
156
168
|
|
|
@@ -437,6 +449,93 @@ class OpenAPI:
|
|
|
437
449
|
self.params[new_endpoint] = params
|
|
438
450
|
return new_endpoint.replace("/{}", "/")
|
|
439
451
|
|
|
452
|
+
def _extract_methods_from_udt(self) -> Dict[str, Any]:
|
|
453
|
+
"""
|
|
454
|
+
Extract HTTP method and endpoint pairings from userDefinedTypes fields.
|
|
455
|
+
|
|
456
|
+
Handles custom router patterns where HTTP method and path are both
|
|
457
|
+
fields in the same userDefinedType entry (e.g., registerRoute('get', '/')).
|
|
458
|
+
|
|
459
|
+
Returns:
|
|
460
|
+
dict: A paths object mapping endpoints to their HTTP method operations.
|
|
461
|
+
"""
|
|
462
|
+
ops = {'get', 'put', 'post', 'delete', 'options', 'head', 'patch'}
|
|
463
|
+
pattern = jmespath.compile(
|
|
464
|
+
'userDefinedTypes[].{file_name: fileName, line_number: lineNumber,'
|
|
465
|
+
' fields: fields[].name}'
|
|
466
|
+
)
|
|
467
|
+
result = pattern.search(self.usages.content)
|
|
468
|
+
if not result:
|
|
469
|
+
return {}
|
|
470
|
+
paths_object: Dict = {}
|
|
471
|
+
for entry in result:
|
|
472
|
+
fields = entry.get('fields') or []
|
|
473
|
+
file_name = entry.get('file_name', '')
|
|
474
|
+
line_number = entry.get('line_number')
|
|
475
|
+
found_ops = [f.strip('"').strip("'") for f in fields
|
|
476
|
+
if f and f.strip('"').strip("'").lower() in ops]
|
|
477
|
+
found_paths = [f.strip('"').strip("'") for f in fields
|
|
478
|
+
if f and '/' in f]
|
|
479
|
+
if not found_ops or not found_paths:
|
|
480
|
+
continue
|
|
481
|
+
for ep in found_paths:
|
|
482
|
+
ep = self._parse_path_regexes(ep)
|
|
483
|
+
path_item: Dict = {}
|
|
484
|
+
for op in found_ops:
|
|
485
|
+
path_item[op.lower()] = {'responses': {}}
|
|
486
|
+
if line_number and file_name:
|
|
487
|
+
ln_entry = create_ln_entries(
|
|
488
|
+
file_name, [line_number], None
|
|
489
|
+
)
|
|
490
|
+
path_item.update(ln_entry)
|
|
491
|
+
if paths_object.get(ep):
|
|
492
|
+
paths_object[ep] = merge_path_objects(
|
|
493
|
+
paths_object[ep], path_item
|
|
494
|
+
)
|
|
495
|
+
else:
|
|
496
|
+
paths_object[ep] = path_item
|
|
497
|
+
return paths_object
|
|
498
|
+
|
|
499
|
+
def _get_java_class_prefixes(self) -> Dict[str, str]:
|
|
500
|
+
"""
|
|
501
|
+
Scans objectSlices for class-level mapping annotations in
|
|
502
|
+
Java/Spring Boot and returns a map of {fileName: prefix_path}.
|
|
503
|
+
|
|
504
|
+
In Spring Boot, a @RequestMapping (or any mapping variant like
|
|
505
|
+
@GetMapping, @PostMapping, etc.) on the class itself acts as a URL
|
|
506
|
+
prefix for all methods in that class. These entries appear in
|
|
507
|
+
objectSlices with an empty usages list and the annotation text in
|
|
508
|
+
the 'code' field.
|
|
509
|
+
|
|
510
|
+
Returns:
|
|
511
|
+
dict: A mapping of file name to its class-level URL path prefix.
|
|
512
|
+
Empty dict for non-Java origins.
|
|
513
|
+
"""
|
|
514
|
+
if self.usages.origin_type not in ('java', 'jar'):
|
|
515
|
+
return {}
|
|
516
|
+
class_mapping_annotations = (
|
|
517
|
+
'@RequestMapping', '@GetMapping', '@PostMapping',
|
|
518
|
+
'@PutMapping', '@DeleteMapping', '@PatchMapping',
|
|
519
|
+
)
|
|
520
|
+
prefixes: Dict[str, str] = {}
|
|
521
|
+
object_slices = self.usages.content.get('objectSlices', [])
|
|
522
|
+
for entry in object_slices:
|
|
523
|
+
code = entry.get('code', '') or ''
|
|
524
|
+
file_name = entry.get('fileName', '') or ''
|
|
525
|
+
usages = entry.get('usages', [])
|
|
526
|
+
# Class-level annotations appear with empty usages and the
|
|
527
|
+
# annotation text in 'code'. We check for all Spring Boot
|
|
528
|
+
# mapping variants that carry a URL path (contains '/').
|
|
529
|
+
if (usages == []
|
|
530
|
+
and code.startswith(class_mapping_annotations)
|
|
531
|
+
and '/' in code
|
|
532
|
+
and file_name
|
|
533
|
+
and file_name not in prefixes):
|
|
534
|
+
extracted = self._extract_endpoints(code)
|
|
535
|
+
if extracted:
|
|
536
|
+
prefixes[file_name] = extracted[0]
|
|
537
|
+
return prefixes
|
|
538
|
+
|
|
440
539
|
def _process_calls(self, method_map: Dict) -> Dict[str, Any]:
|
|
441
540
|
"""
|
|
442
541
|
Process calls and return a new method map.
|
|
@@ -135,15 +135,15 @@ def test_endpoints_to_openapi(java_usages_1):
|
|
|
135
135
|
'put': {'responses': {}},
|
|
136
136
|
'x-atom-usages': {'call': {'account-service/src/main/java/com/piggymetrics/account/controller/AccountController.java': [25,
|
|
137
137
|
30],
|
|
138
|
-
'auth-service/src/main/java/com/piggymetrics/auth/controller/UserController.java': [22],
|
|
139
|
-
'notification-service/src/main/java/com/piggymetrics/notification/controller/RecipientController.java': [21,
|
|
140
|
-
26],
|
|
141
138
|
'statistics-service/src/main/java/com/piggymetrics/statistics/controller/StatisticsController.java': [20]},
|
|
142
|
-
'target': {'account-service/src/main/java/com/piggymetrics/account/controller/AccountController.java': [30],
|
|
143
|
-
'auth-service/src/main/java/com/piggymetrics/auth/controller/UserController.java': [22],
|
|
144
|
-
'notification-service/src/main/java/com/piggymetrics/notification/controller/RecipientController.java': [26]}}},
|
|
139
|
+
'target': {'account-service/src/main/java/com/piggymetrics/account/controller/AccountController.java': [30]}}},
|
|
145
140
|
'/latest': {'get': {'responses': {}},
|
|
146
141
|
'x-atom-usages': {'call': {'statistics-service/src/main/java/com/piggymetrics/statistics/client/ExchangeRatesClient.java': [13]}}},
|
|
142
|
+
'/recipients/current': {'get': {'responses': {}},
|
|
143
|
+
'put': {'responses': {}},
|
|
144
|
+
'x-atom-usages': {'call': {'notification-service/src/main/java/com/piggymetrics/notification/controller/RecipientController.java': [21,
|
|
145
|
+
26]},
|
|
146
|
+
'target': {'notification-service/src/main/java/com/piggymetrics/notification/controller/RecipientController.java': [26]}}},
|
|
147
147
|
'/statistics/{accountName}': {'parameters': [{'in': 'path',
|
|
148
148
|
'name': 'accountName',
|
|
149
149
|
'required': True}],
|
|
@@ -151,6 +151,9 @@ def test_endpoints_to_openapi(java_usages_1):
|
|
|
151
151
|
'x-atom-usages': {'call': {'account-service/src/main/java/com/piggymetrics/account/client/StatisticsServiceClient.java': [13]}}},
|
|
152
152
|
'/uaa/users': {'post': {'responses': {}},
|
|
153
153
|
'x-atom-usages': {'call': {'account-service/src/main/java/com/piggymetrics/account/client/AuthServiceClient.java': [12]}}},
|
|
154
|
+
'/users/current': {'get': {'responses': {}},
|
|
155
|
+
'x-atom-usages': {'call': {'auth-service/src/main/java/com/piggymetrics/auth/controller/UserController.java': [22]},
|
|
156
|
+
'target': {'auth-service/src/main/java/com/piggymetrics/auth/controller/UserController.java': [22]}}},
|
|
154
157
|
'/{accountName}': {'get': {'responses': {}},
|
|
155
158
|
'parameters': [{'in': 'path',
|
|
156
159
|
'name': 'accountName',
|
|
@@ -1356,3 +1359,36 @@ def test_rb(rb_usages_1):
|
|
|
1356
1359
|
def test_rb_with_endpoints(rb_usages_2):
|
|
1357
1360
|
result = ruby_convert(rb_usages_2.usages)
|
|
1358
1361
|
assert result
|
|
1362
|
+
|
|
1363
|
+
|
|
1364
|
+
@pytest.fixture
|
|
1365
|
+
def ts_usages_1():
|
|
1366
|
+
return OpenAPI('openapi3.0.1', 'typescript',
|
|
1367
|
+
'test/data/ts-custom-router-usages.json')
|
|
1368
|
+
|
|
1369
|
+
|
|
1370
|
+
def test_ts_custom_router(ts_usages_1):
|
|
1371
|
+
result = ts_usages_1.convert_usages()
|
|
1372
|
+
result = sort_openapi_result(result)
|
|
1373
|
+
assert result
|
|
1374
|
+
# Verify HTTP methods are present for endpoints with registerRoute pattern
|
|
1375
|
+
assert '/' in result
|
|
1376
|
+
assert 'get' in result['/']
|
|
1377
|
+
assert 'post' in result['/']
|
|
1378
|
+
assert '/complex' in result
|
|
1379
|
+
assert 'get' in result['/complex']
|
|
1380
|
+
|
|
1381
|
+
|
|
1382
|
+
def test_ts_custom_router_openapi(ts_usages_1):
|
|
1383
|
+
result = ts_usages_1.endpoints_to_openapi()
|
|
1384
|
+
result = sort_openapi_result(result)
|
|
1385
|
+
paths = result['paths']
|
|
1386
|
+
# Check that methods from registerRoute are populated
|
|
1387
|
+
assert 'get' in paths['/']
|
|
1388
|
+
assert 'post' in paths['/']
|
|
1389
|
+
assert 'get' in paths['/complex']
|
|
1390
|
+
# Verify the standard paths also exist
|
|
1391
|
+
assert '/api' in paths
|
|
1392
|
+
assert '/items' in paths
|
|
1393
|
+
assert '/users' in paths
|
|
1394
|
+
assert '/render' in paths
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|