Fix cache/complement/index/skip attributes shadowing Table methods - #705
Fix cache/complement/index/skip attributes shadowing Table methods#705gaoflow wants to merge 1 commit into
Conversation
PR Summary by QodoFix Table method shadowing by cache/complement/index/skip view attributes
AI Description
Diagram
High-Level Assessment
Files changed (9)
|
Coverage Report for CI Build 30894017993Coverage increased (+0.2%) to 91.864%Details
Uncovered Changes
Coverage RegressionsNo coverage regressions found. Coverage Stats
💛 - Coveralls |
Code Review by Qodo
Context used 1. Test scan traverses tests
|
| for dirpath, _, filenames in os.walk(root): | ||
| if os.path.basename(dirpath) == 'test': | ||
| continue |
There was a problem hiding this comment.
1. Test scan traverses tests 🐞 Bug ☼ Reliability
test_no_shadowed_api_names() attempts to skip the petl/test directory, but because it doesn’t prune os.walk() traversal it still parses petl/test/* subdirectories. This makes the scan depend on test-only modules and can break or misbehave under non-UTF8 default encodings when reading test sources containing non-ASCII characters.
Agent Prompt
### Issue description
`test_no_shadowed_api_names()` uses `os.walk()` and `continue` to skip a directory named `test`, but this does not prevent `os.walk()` from descending into `petl/test/...`. As a result, the scan parses test modules too, and reads them with `open(path)` using the platform default encoding.
### Issue Context
Some `petl/test/...` files contain non-ASCII source (e.g., `u"é"`). Reading them via `open(path)` without honoring the source encoding cookie makes the test less portable and can fail under ASCII (or otherwise incompatible) locales.
### Fix Focus Areas
- petl/test/test_method_shadow.py[314-328]
### What to change
- Prune traversal by capturing `dirnames` from `os.walk()` and removing `test` (and potentially `__pycache__`) in-place so subdirectories under `petl/test` aren’t visited.
- Use an encoding-aware opener for Python source (e.g., `tokenize.open` on Py3, or a small helper that respects `# -*- coding: ... -*-`) instead of `open(path)`.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| records = [{'foo': 'a', 'bar': 1}, {'foo': 'b', 'bar': 2}, | ||
| {'foo': 'c', 'bar': 3}] | ||
| eq_([('c', 3)], | ||
| list(view._read_rows_from(records, ('foo', 'bar')))) |
There was a problem hiding this comment.
2. Dict-order dependent test 🐞 Bug ☼ Reliability
test_fromavro_skip() constructs plain dict records but AvroView._map_row_from() uses tuple(record.values()) on Python 3, so output column order follows mapping iteration order rather than the provided header. This makes the new test depend on dict-order guarantees (or implementation details) instead of explicitly modeling schema/header ordering.
Agent Prompt
### Issue description
`test_fromavro_skip()` passes a list of plain dicts to `_read_rows_from()` and asserts a specific tuple shape/order, but the production code path for PY3 uses `tuple(record.values())`, which is driven by mapping iteration order.
### Issue Context
Even though CPython 3.7+ guarantees insertion order for `dict`, using an explicitly ordered mapping in the test makes the fixture deterministic and clearly documents the ordering contract the test expects.
### Fix Focus Areas
- petl/test/test_method_shadow.py[211-217]
### What to change
- Build `records` as `collections.OrderedDict([...])` (or another explicitly ordered mapping) in `test_fromavro_skip()`.
- Alternatively (broader change), update `AvroView._map_row_from()` to use `header` ordering on PY3 too (e.g., `tuple(record.get(col) for col in header)` when `header` is provided).
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
917d7c2 to
f38d20d
Compare
Table subclasses that store a constructor argument under a name the
fluent API already binds are unusable through that method: the instance
attribute wins attribute lookup, so calling it raises TypeError.
>>> import petl as etl
>>> etl.sort([['foo'], ['a']], 'foo').cache()
TypeError: 'bool' object is not callable
Twelve views were still affected after petl-developers#697 and petl-developers#704: cache on
HashJoinView, HashLeftJoinView, HashRightJoinView, SortView and
CacheView; complement on SearchView, RowSelectView and FieldSelectView;
index on AddFieldView and MoveFieldView; skip on AvroView and BcolzView.
Rename each stored attribute with a leading underscore, as 5498411 (petl-developers#555)
did for header and petl-developers#697 did for dicts. None of these are documented as
readable attributes, so no compatibility property is needed here.
Adds a test that scans the source for the same collision, so the next one
fails in CI instead of at the call site.
f38d20d to
ce05d50
Compare
|
Both review findings are addressed in the head commit:
29/29 checks green. |
Follow-up to #697 and #704, finishing the same collision class.
The class
Tablegets most of its fluent API from module-level assignments(
Table.cache = cache,Table.complement = complement,Table.skip = skip)plus the methods it inherits from
IterContainer(index,min,list, ...) —263 callables in total. Views then store their constructor arguments as instance
attributes of the same name. Python resolves the instance attribute first, so the
method is gone and calling it raises
TypeError:5498411 (#555) hit this on
header, #697 ondicts, #704 on the remainingthirteen
headersites. I went back over the whole surface rather than the onecall I tripped over, by intersecting the 263-name API with every
self.<name> =assignment in a
Tablesubclass. Twelve views were still affected:cache()HashJoinViewtransform/hashjoins.pycache()HashLeftJoinViewtransform/hashjoins.pycache()HashRightJoinViewtransform/hashjoins.pycache()SortViewtransform/sorts.pycache()CacheViewutil/materialise.pycomplement()SearchViewtransform/regex.pycomplement()RowSelectViewtransform/selects.pycomplement()FieldSelectViewtransform/selects.pyindex()AddFieldViewtransform/basics.pyindex()MoveFieldViewtransform/basics.pyskip()AvroViewio/avro.pyskip()BcolzViewio/bcolz.pyAll twelve raise today; all twelve pass after the change, and the scan now comes
back empty.
The fix
Rename the stored attribute with a leading underscore, the convention 5498411
introduced for
headerand #697 followed fordicts.The compatibility question that came up on #697 does not arise here.
.dictswasthe caller's own input data, so that PR kept it readable through a property. These
twelve are internal flags and offsets (
cache=True,complement=False,index=0,skip=0) — nothing in the docs, examples or tests reads them back off a view, andgrepfinds no reader outside the class that owns them. So a plain rename, no shim.I also looked at fixing this at the root, in
Tableitself: a__setattr__thatrefuses to store an attribute shadowing a method would catch the whole class
permanently. It would put a Python-level call on every attribute write in every
view for a problem that only bites at construction time, and it would break any
downstream subclass doing the same thing. Not worth it — a test can do the same
job for free.
Tests
test_method_shadow.pygrows from the thirteenheadercases to cover the fournew names: one test per view calling the method that used to raise, plus tests
that the renamed attributes still carry their options (
cache=Falsereallydisables caching,
complement=Truereally inverts the selection,index=0reallypositions the field,
skip=nreally skips).fromavro/frombcolzare drivenwithout fastavro and bcolz installed, since neither is needed to reach the
skipping code.
test_no_shadowed_api_namesis the one that matters: it walks the package,finds every
Tablesubclass, and fails if any of them assigns an instanceattribute named after an API method — reporting file, line, class and name. It
reproduces all twelve sites on master and would have caught #555, #643 and #704
as well. A deliberate class-level override (
DictsView.dicts, the property #697added) is not flagged.
586 pass, 13 skip;
pytest --doctest-modules739 pass, 21 skip. I mutated eachrenamed read in both directions — leaving a site shadowed, and dropping or
inverting the option the attribute carries — and every mutant is caught.