Skip to content

Commit 4b39ad1

Browse files
committed
Present python-function-structure-comparator with reviewed source and examples
0 parents  commit 4b39ad1

15 files changed

Lines changed: 1085 additions & 0 deletions

File tree

‎.github/workflows/verify.yml‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
name: verify
2+
3+
on:
4+
push:
5+
pull_request:
6+
7+
permissions:
8+
contents: read
9+
10+
jobs:
11+
test:
12+
runs-on: ubuntu-latest
13+
steps:
14+
- uses: actions/checkout@v4
15+
- uses: actions/setup-python@v5
16+
with:
17+
python-version: "3.12"
18+
- run: python -m pip install --upgrade pip build
19+
- run: python -m unittest discover -s tests -v
20+
- run: python -m compileall -q function_structure_comparator
21+
- run: python -m build

‎.gitignore‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
__pycache__/
2+
*.py[cod]
3+
.pytest_cache/
4+
.venv/
5+
build/
6+
dist/
7+
*.egg-info/
8+
output/
9+
tagged_messages.jsonl
10+
run_summary.json
11+
conversations*.json

‎LICENSE.md‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2025 Cinvana Lab
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

‎ORIGIN.md‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Origin
2+
3+
Published from `Donor/blacksmith_compare.py`, an authored comparison engine built for the Blacksmith package-review work. The public repository keeps the original multi-signal comparator, adds a CLI and synthetic examples, and removes surrounding private package inventories.

‎README.md‎

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Python Function Structure Comparator
2+
3+
Look for related Python functions beyond a plain text diff.
4+
5+
Compare Python functions or rank likely variants using syntax, signature, calls, and literals without importing or executing target code.
6+
7+
## See it work
8+
9+
**Input:** The supplied before.py and after.py example functions.
10+
11+
**Result:** A file-pair report with separate comparison metrics and possible_variant classifications.
12+
13+
[Read the captured output](examples/result.txt) | [Inspect the example](examples/before.py)
14+
15+
Python 3.11 or newer. From the repository root:
16+
17+
```sh
18+
python -m pip install -e .
19+
python -m function_structure_comparator.cli files examples/before.py examples/after.py
20+
```
21+
22+
The example uses synthetic material and runs offline. The captured output comes from executing this example, not a hand-written mockup.
23+
24+
## How it works
25+
26+
The comparator parses source without executing it, then separates names, signatures, normalized source, AST structure, call names, literals, and control-flow features. Its report keeps the individual scores beside a bounded relationship label so the label can be inspected.
27+
28+
Implementation: [function_structure_comparator/core.py](function_structure_comparator/core.py), [function_structure_comparator/cli.py](function_structure_comparator/cli.py), [examples/before.py](examples/before.py).
29+
30+
## Limits
31+
32+
Structural similarity does not prove equivalence, correctness, authorship, or plagiarism. Imports and function bodies are parsed, not run. Runtime values and side effects are outside the analysis.
33+
34+
[Reference and CLI details](docs/REFERENCE.md) | [Origin](ORIGIN.md) | [MIT license](LICENSE.md)

‎SECURITY.md‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# Security
2+
3+
- Python files are read and parsed with `ast`; they are not imported or executed.
4+
- Reports may contain normalized source, names, literals, and local filenames. Parent paths are omitted, but source itself can still contain private paths or secrets. Review reports before publication.
5+
- The supported file/source-string APIs do not import code. The internal general-object comparison helper is not a sandbox and may invoke ordinary Python representation or equality methods if called directly with live objects.
6+
- Similarity scores are heuristic and should not drive destructive deduplication automatically.

‎docs/REFERENCE.md‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Python Function Structure Comparator
2+
3+
Compare Python functions without executing them. The report separates exact name/signature matches, normalized source, AST structure, call names, attribute calls, literals, branch/loop/yield/return features, and a bounded relationship classification.
4+
5+
```powershell
6+
python -m unittest discover -s tests -v
7+
python -m function_structure_comparator.cli files examples/before.py examples/after.py
8+
```
9+
10+
It can compare two files or rank a candidate function against a Python/JSON inventory. This is useful for code archaeology, duplicate-capability detection, migration review, and identifying strong variants that a plain text diff obscures.
11+
12+
Similarity is evidence, not proof of authorship, correctness, equivalence, or plagiarism. Dynamic behavior, imports, side effects, and runtime values are not evaluated. Reports name input files but omit their parent paths by default.
13+
14+
See [ORIGIN.md](../ORIGIN.md) and [SECURITY.md](../SECURITY.md).

‎examples/after.py‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
def normalize_name(value: str, *, empty: str = "unknown") -> str:
2+
cleaned = value.strip().lower()
3+
return cleaned or empty

‎examples/before.py‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
def normalize_name(value: str) -> str:
2+
return value.strip().lower()

‎examples/result.txt‎

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
{
2+
"mode": "file_to_file",
3+
"file_a": "before.py",
4+
"file_b": "after.py",
5+
"comparison": {
6+
"general_object_comparison": {
7+
"identity": {
8+
"same_object_identity": false,
9+
"same_type": true,
10+
"type_a": "builtins.str",
11+
"type_b": "builtins.str"
12+
},
13+
"exactness": {
14+
"python_equality_operator": false,
15+
"repr_exact_match": false,
16+
"json_exact_match": false,
17+
"json_hash_a": "e1428c62183596ba10d7823e3a78440f86692b2312e79786370f0e12ac4b84a9",
18+
"json_hash_b": "d68c9d251bf2f1a177c72951e0b32128f8ba5bdb3f760d815857c17fa0931584"
19+
},
20+
"similarity": {
21+
"repr_sequence_similarity": 0.6824644549763034,
22+
"json_sequence_similarity": 0.676056338028169,
23+
"json_line_similarity": 0.0,
24+
"json_token_jaccard_similarity": 0.6956521739130435,
25+
"json_token_multiset_overlap": 0.5869565217391305
26+
},
27+
"python_source_comparison": {
28+
"source_available_a": true,
29+
"source_available_b": true,
30+
"source_exact_match": false,
31+
"source_normalized_text_similarity": 0.6702127659574468,
32+
"source_line_similarity": 0.0,
33+
"source_token_jaccard_similarity": 0.6190476190476191,
34+
"source_token_multiset_overlap": 0.5833333333333334,
35+
"ast_normalized_exact_match": false,
36+
"ast_normalized_similarity": 0.6767676767676768,
37+
"ast_dump_exact_match": false,
38+
"ast_dump_similarity": 0.3274336283185841
39+
},
40+
"previews": {
41+
"repr_a": "'def normalize_name(value: str) -> str:\\n return value.strip().lower()\\n'",
42+
"repr_b": "'def normalize_name(value: str, *, empty: str = \"unknown\") -> str:\\n cleaned = value.strip().lower()\\n return cleaned or empty\\n'",
43+
"json_a": "\"def normalize_name(value: str) -> str:\\n return value.strip().lower()\\n\"",
44+
"json_b": "\"def normalize_name(value: str, *, empty: str = \\\"unknown\\\") -> str:\\n cleaned = value.strip().lower()\\n return cleaned or empty\\n\""
45+
}
46+
},
47+
"function_specific_comparison": {
48+
"function_like_comparison_possible": true,
49+
"features_a": {
50+
"function_found": true,
51+
"name": "normalize_name",
52+
"signature": {
53+
"name": "normalize_name",
54+
"is_async": false,
55+
"positional_only": [],
56+
"positional_or_keyword": [
57+
"value"
58+
],
59+
"vararg": null,
60+
"keyword_only": [],
61+
"kwarg": null,
62+
"arg_count_total": 1,
63+
"decorator_count": 0
64+
},
65+
"body_normalized": "return value.strip().lower()",
66+
"body_ast_dump": "Module(body=[Return(value=Call(func=Attribute(value=Call(func=Attribute(value=Name(id='value', ctx=Load()), attr='strip', ctx=Load())), attr='lower', ctx=Load())))])",
67+
"return_count": 1,
68+
"yield_count": 0,
69+
"call_names": [],
70+
"attribute_call_names": [
71+
"lower",
72+
"strip"
73+
],
74+
"raises": 0,
75+
"branch_count": 0,
76+
"loop_count": 0,
77+
"comprehension_count": 0,
78+
"string_literals": []
79+
},
80+
"features_b": {
81+
"function_found": true,
82+
"name": "normalize_name",
83+
"signature": {
84+
"name": "normalize_name",
85+
"is_async": false,
86+
"positional_only": [],
87+
"positional_or_keyword": [
88+
"value"
89+
],
90+
"vararg": null,
91+
"keyword_only": [
92+
"empty"
93+
],
94+
"kwarg": null,
95+
"arg_count_total": 2,
96+
"decorator_count": 0
97+
},
98+
"body_normalized": "cleaned = value.strip().lower()\nreturn cleaned or empty",
99+
"body_ast_dump": "Module(body=[Assign(targets=[Name(id='cleaned', ctx=Store())], value=Call(func=Attribute(value=Call(func=Attribute(value=Name(id='value', ctx=Load()), attr='strip', ctx=Load())), attr='lower', ctx=Load()))), Return(value=BoolOp(op=Or(), values=[Name(id='cleaned', ctx=Load()), Name(id='empty', ctx=Load())]))])",
100+
"return_count": 1,
101+
"yield_count": 0,
102+
"call_names": [],
103+
"attribute_call_names": [
104+
"lower",
105+
"strip"
106+
],
107+
"raises": 0,
108+
"branch_count": 0,
109+
"loop_count": 0,
110+
"comprehension_count": 0,
111+
"string_literals": [
112+
"unknown"
113+
]
114+
},
115+
"name_exact_match": true,
116+
"signature_exact_match": false,
117+
"signature_similarity": 0.47901234567901235,
118+
"body_normalized_exact_match": false,
119+
"body_normalized_similarity": 0.5783132530120482,
120+
"body_ast_exact_match": false,
121+
"body_ast_similarity": 0.16842105263157894,
122+
"call_name_jaccard_similarity": 1.0,
123+
"attribute_call_name_jaccard_similarity": 1.0,
124+
"string_literal_jaccard_similarity": 0.0,
125+
"structure_similarity_summary": 0.6451493302645279,
126+
"likely_relationship": "possible_variant"
127+
}
128+
}
129+
}

0 commit comments

Comments
 (0)