Skip to content

Commit dd70a7a

Browse files
authored
fea: refactor globals and exclude diff merge with targets (#102)
1 parent 33d87ba commit dd70a7a

5 files changed

Lines changed: 25 additions & 178 deletions

File tree

objwatch/constants.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
# MIT License
22
# Copyright (c) 2025 aeeeeeep
33

4-
54
from enum import Enum
65
from types import FunctionType
76

objwatch/targets.py

Lines changed: 12 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,6 @@ def __init__(self, targets: TargetsType, exclude_targets: Optional[TargetsType]
9090
self.filename_targets: Set = set()
9191
self.targets: dict = self._process_targets(targets)
9292
self.exclude_targets: dict = self._process_targets(exclude_targets)
93-
self.processed_targets: dict = self._diff_targets()
9493

9594
def _check_targets(
9695
self, targets: TargetsType, exclude_targets: Optional[TargetsType]
@@ -330,7 +329,7 @@ def _parse_module_by_name(self, module_name: str, recursive: bool = True) -> dic
330329
# Add submodule structure to current module
331330
module_structure[submodule_name] = submodule_structure
332331
except Exception as e:
333-
log_warn(f"Failed to parse submodule {full_submodule_name}: {str(e)}")
332+
log_warn(f"Failed to parse submodule '{full_submodule_name}': {str(e)}")
334333

335334
return module_structure
336335

@@ -415,86 +414,6 @@ def _flatten_module_structure(self, module_path: str, module_structure: dict, re
415414
submodule_path = f"{module_path}.{key}"
416415
self._flatten_module_structure(submodule_path, value, result)
417416

418-
def _diff_targets(self) -> dict:
419-
"""Calculate effective targets by excluding specified patterns.
420-
421-
Returns:
422-
Filtered targets dictionary after applying exclusion rules
423-
"""
424-
filtered_targets: dict = {}
425-
for module_path, target_details in self.targets.items():
426-
exclude_details = self.exclude_targets.get(module_path, {})
427-
428-
# Handle track_all exclusion at the module level
429-
filtered_classes = self._diff_level(target_details.get('classes', {}), exclude_details.get('classes', {}))
430-
431-
# Calculate filtered target details
432-
filtered_details = {
433-
'classes': filtered_classes,
434-
'functions': list(set(target_details.get('functions', [])) - set(exclude_details.get('functions', []))),
435-
'globals': list(set(target_details.get('globals', [])) - set(exclude_details.get('globals', []))),
436-
}
437-
438-
# Process nested submodules
439-
for key, value in target_details.items():
440-
if key not in ['classes', 'functions', 'globals'] and isinstance(value, dict):
441-
submodule_path = f"{module_path}.{key}"
442-
submodule_exclude = exclude_details.get(key, {})
443-
filtered_sub = self._diff_level(value, submodule_exclude)
444-
if filtered_sub:
445-
filtered_details[key] = filtered_sub
446-
447-
# Flatten the module structure
448-
self._flatten_module_structure(module_path, filtered_details, filtered_targets)
449-
450-
return filtered_targets
451-
452-
def _diff_level(self, target: dict, exclude: dict) -> dict:
453-
"""Recursively filter nested structures by exclusion rules.
454-
455-
Args:
456-
target: Original nested structure (dict of dicts)
457-
exclude: Exclusion patterns to apply
458-
459-
Returns:
460-
dict: Filtered structure with empty containers preserved
461-
"""
462-
diff = {}
463-
for key, value in target.items():
464-
filtered = None
465-
466-
if key in exclude:
467-
if isinstance(value, dict) and isinstance(exclude[key], dict):
468-
# Handle class details with track_all flag
469-
if (
470-
'track_all' in value
471-
and value['track_all']
472-
and 'track_all' in exclude[key]
473-
and exclude[key]['track_all']
474-
):
475-
# If both are tracking all, exclude the entire class
476-
filtered = None
477-
else:
478-
filtered = self._diff_level(value, exclude[key])
479-
elif isinstance(value, list) and isinstance(exclude[key], list):
480-
filtered = list(set(value) - set(exclude[key])) # type: ignore
481-
elif value != exclude[key]:
482-
filtered = value
483-
else:
484-
filtered = value
485-
486-
if filtered is None:
487-
# Skip this key entirely (excluded)
488-
continue
489-
elif isinstance(filtered, dict):
490-
diff[key] = filtered
491-
elif isinstance(filtered, list):
492-
diff[key] = filtered if filtered else []
493-
else:
494-
diff[key] = filtered
495-
496-
return diff
497-
498417
def _process_assignment(self, node: ast.Assign, result: dict):
499418
"""Extract global variables from assignment AST nodes.
500419
@@ -506,7 +425,7 @@ def _process_assignment(self, node: ast.Assign, result: dict):
506425
node: AST assignment node to analyze
507426
result: dict to update with found globals
508427
"""
509-
if any(isinstance(parent, ast.ClassDef) for parent in iter_parents(node)):
428+
if any(isinstance(parent, (ast.ClassDef, ast.FunctionDef)) for parent in iter_parents(node)):
510429
return
511430

512431
for assign_target in node.targets:
@@ -517,14 +436,14 @@ def _process_assignment(self, node: ast.Assign, result: dict):
517436
if isinstance(element, ast.Name):
518437
result['globals'].append(element.id)
519438

520-
def get_processed_targets(self) -> dict:
521-
"""Retrieve final monitoring targets after exclusion processing.
439+
def get_targets(self) -> dict:
440+
"""Retrieve targets.
522441
523442
Returns:
524-
dict: Filtered dictionary containing:
525-
- classes: Non-excluded class methods
526-
- functions: Non-excluded functions
527-
- globals: Non-excluded global variables
443+
dict: Target dictionary containing:
444+
- classes: Class methods
445+
- functions: Functions
446+
- globals: Global variables
528447
529448
Example:
530449
{
@@ -540,7 +459,7 @@ def get_processed_targets(self) -> dict:
540459
}
541460
}
542461
"""
543-
return self.processed_targets
462+
return self.targets
544463

545464
def get_exclude_targets(self) -> dict:
546465
"""Retrieve excluded targets.
@@ -588,12 +507,12 @@ def target_handler(o):
588507
return o.__dict__
589508
return str(o)
590509

591-
if len(self.processed_targets) > Constants.MAX_TARGETS_DISPLAY:
592-
truncated_obj = {key: "..." for key in self.processed_targets.keys()}
510+
if len(self.targets) > Constants.MAX_TARGETS_DISPLAY:
511+
truncated_obj = {key: "..." for key in self.targets.keys()}
593512
truncated_obj["Warning: too many top-level keys, only showing values like"] = "..."
594513
return json.dumps(truncated_obj, indent=indent, default=target_handler)
595514

596-
return json.dumps(self.processed_targets, indent=indent, default=target_handler)
515+
return json.dumps(self.targets, indent=indent, default=target_handler)
597516

598517
def get_filename_targets(self) -> Set:
599518
"""Get monitored filesystem paths.

objwatch/tracer.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,12 +58,12 @@ def __init__(
5858
# Process and determine the set of target files to monitor
5959
targets_cls = Targets(self.config.targets, self.config.exclude_targets)
6060
self.filename_targets: Set = targets_cls.get_filename_targets()
61-
self.targets: dict = targets_cls.get_processed_targets()
61+
self.targets: dict = targets_cls.get_targets()
6262
self.exclude_targets: dict = targets_cls.get_exclude_targets()
6363
self._build_target_index()
6464
self._build_exclude_target_index()
6565
log_debug(
66-
f"\nProcessed targets:\n{'>' * 10}\n"
66+
f"\nTargets:\n{'>' * 10}\n"
6767
+ targets_cls.serialize_targets()
6868
+ f"\n{'<' * 10}\n"
6969
+ f"Filename targets:\n{'>' * 10}\n"

tests/test_base.py

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -389,7 +389,7 @@ def test_unsupport_wrapper(self):
389389

390390
class TestTargetsStr(unittest.TestCase):
391391
def test_targets_with_submodules(self):
392-
processed = Targets(['importlib']).get_processed_targets()
392+
processed = Targets(['importlib']).get_targets()
393393
self.assertIn('importlib', processed)
394394
module_info = processed['importlib']
395395

@@ -399,16 +399,9 @@ def test_targets_with_submodules(self):
399399

400400
expected_globals = [
401401
"_pack_uint32",
402-
"level",
403-
"target",
404-
"pkgpath",
405-
"parent",
406-
"spec",
407402
"__all__",
408-
"name",
409403
"_RELOADING",
410404
"_unpack_uint32",
411-
"parent_name",
412405
]
413406
for global_var in expected_globals:
414407
self.assertIn(global_var, module_info.get('globals', []))
@@ -418,7 +411,7 @@ def test_targets_with_submodules(self):
418411

419412
class TestTargetsModule(unittest.TestCase):
420413
def test_targets_with_submodules(self):
421-
processed = Targets([importlib]).get_processed_targets()
414+
processed = Targets([importlib]).get_targets()
422415

423416
self.assertIn('importlib', processed)
424417
module_info = processed['importlib']
@@ -429,16 +422,9 @@ def test_targets_with_submodules(self):
429422

430423
expected_globals = [
431424
"_pack_uint32",
432-
"level",
433-
"target",
434-
"pkgpath",
435-
"parent",
436-
"spec",
437425
"__all__",
438-
"name",
439426
"_RELOADING",
440427
"_unpack_uint32",
441-
"parent_name",
442428
]
443429
for global_var in expected_globals:
444430
self.assertIn(global_var, module_info.get('globals', []))

tests/test_targets.py

Lines changed: 9 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ def setUp(self):
1212

1313
def test_module_monitoring(self):
1414
targets = Targets(['tests.utils.example_targets.sample_module'])
15-
processed = targets.get_processed_targets()
15+
processed = targets.get_targets()
1616

1717
self.assertIn('tests.utils.example_targets.sample_module', processed)
1818
mod = processed['tests.utils.example_targets.sample_module']
@@ -22,97 +22,40 @@ def test_module_monitoring(self):
2222

2323
def test_class_definition(self):
2424
targets = Targets(['tests.utils.example_targets.sample_module:SampleClass'])
25-
processed = targets.get_processed_targets()
25+
processed = targets.get_targets()
2626

2727
cls_info = processed['tests.utils.example_targets.sample_module']['classes']['SampleClass']
2828
self.assertTrue(cls_info.get("track_all", False))
2929

3030
def test_class_attribute(self):
3131
targets = Targets(['tests.utils.example_targets.sample_module:SampleClass.class_attr'])
32-
processed = targets.get_processed_targets()
32+
processed = targets.get_targets()
3333

3434
cls_info = processed['tests.utils.example_targets.sample_module']['classes']['SampleClass']
3535
self.assertIn('class_attr', cls_info['attributes'])
3636

3737
def test_class_method(self):
3838
targets = Targets(['tests.utils.example_targets.sample_module:SampleClass.class_method()'])
39-
processed = targets.get_processed_targets()
39+
processed = targets.get_targets()
4040

4141
cls_info = processed['tests.utils.example_targets.sample_module']['classes']['SampleClass']
4242
self.assertIn('class_method', cls_info['methods'])
4343

4444
def test_function_target(self):
4545
targets = Targets(['tests.utils.example_targets.sample_module:module_function()'])
46-
processed = targets.get_processed_targets()
46+
processed = targets.get_targets()
4747

4848
self.assertIn('module_function', processed['tests.utils.example_targets.sample_module']['functions'])
4949

5050
def test_global_variable(self):
5151
targets = Targets(['tests.utils.example_targets.sample_module::GLOBAL_VAR'])
52-
processed = targets.get_processed_targets()
52+
processed = targets.get_targets()
5353

5454
self.assertIn('GLOBAL_VAR', processed['tests.utils.example_targets.sample_module']['globals'])
5555

56-
def test_exclusion_rules(self):
57-
targets = Targets(
58-
['tests.utils.example_targets.sample_module'],
59-
exclude_targets=['tests.utils.example_targets.sample_module:SampleClass.class_method()'],
60-
)
61-
processed = targets.get_processed_targets()
62-
63-
cls_info = processed['tests.utils.example_targets.sample_module']['classes']['SampleClass']
64-
self.assertNotIn('class_method', cls_info['methods'])
65-
66-
def test_exclude_class_attribute(self):
67-
targets = Targets(
68-
['tests.utils.example_targets.sample_module'],
69-
exclude_targets=['tests.utils.example_targets.sample_module:SampleClass.class_attr'],
70-
)
71-
processed = targets.get_processed_targets()
72-
73-
cls_info = processed['tests.utils.example_targets.sample_module']['classes']['SampleClass']
74-
self.assertNotIn('class_attr', cls_info['attributes'])
75-
76-
def test_exclude_module_function(self):
77-
targets = Targets(
78-
['tests.utils.example_targets.sample_module'],
79-
exclude_targets=['tests.utils.example_targets.sample_module:module_function()'],
80-
)
81-
processed = targets.get_processed_targets()
82-
83-
mod_info = processed['tests.utils.example_targets.sample_module']
84-
self.assertNotIn('module_function', mod_info['functions'])
85-
86-
def test_exclude_global_variable(self):
87-
targets = Targets(
88-
['tests.utils.example_targets.sample_module'],
89-
exclude_targets=['tests.utils.example_targets.sample_module::GLOBAL_VAR'],
90-
)
91-
processed = targets.get_processed_targets()
92-
93-
mod_info = processed['tests.utils.example_targets.sample_module']
94-
self.assertNotIn('GLOBAL_VAR', mod_info['globals'])
95-
96-
def test_mixed_exclusions(self):
97-
targets = Targets(
98-
['tests.utils.example_targets.sample_module'],
99-
exclude_targets=[
100-
'tests.utils.example_targets.sample_module:SampleClass.static_method()',
101-
'tests.utils.example_targets.sample_module::GLOBAL_VAR',
102-
'tests.utils.example_targets.sample_module:module_function()',
103-
],
104-
)
105-
processed = targets.get_processed_targets()
106-
107-
mod_info = processed['tests.utils.example_targets.sample_module']
108-
cls_info = mod_info['classes']['SampleClass']
109-
self.assertNotIn('static_method', cls_info['methods'])
110-
self.assertNotIn('GLOBAL_VAR', mod_info['globals'])
111-
self.assertNotIn('module_function', mod_info['functions'])
112-
11356
def test_object_module_monitoring(self):
11457
targets = Targets([sample_module])
115-
processed = targets.get_processed_targets()
58+
processed = targets.get_targets()
11659

11760
self.assertIn(sample_module.__name__, processed)
11861
mod = processed[sample_module.__name__]
@@ -124,7 +67,7 @@ def test_object_class_methods(self):
12467
from tests.utils.example_targets.sample_module import SampleClass
12568

12669
targets = Targets([SampleClass.class_method, SampleClass.static_method, SampleClass.method])
127-
processed = targets.get_processed_targets()
70+
processed = targets.get_targets()
12871

12972
cls_info = processed[sample_module.__name__]['classes']['SampleClass']
13073
self.assertIn('class_method', cls_info['methods'])
@@ -133,7 +76,7 @@ def test_object_class_methods(self):
13376

13477
def test_object_functions_and_globals(self):
13578
targets = Targets([sample_module.module_function, "tests.utils.example_targets.sample_module::GLOBAL_VAR"])
136-
processed = targets.get_processed_targets()
79+
processed = targets.get_targets()
13780

13881
mod_info = processed[sample_module.__name__]
13982
self.assertIn('module_function', mod_info['functions'])

0 commit comments

Comments
 (0)