|
9 | 9 | import json |
10 | 10 | import socket as stdlib_socket |
11 | 11 | import sys |
12 | | -import tokenize |
13 | 12 | import types |
14 | 13 | from pathlib import Path, PurePath |
15 | 14 | from types import ModuleType |
@@ -576,93 +575,26 @@ def test_classes_are_final() -> None: |
576 | 575 |
|
577 | 576 |
|
578 | 577 | def test_pyright_recognizes_init_attributes() -> None: |
579 | | - """Check whether we provide `alias` for all underscore prefixed attributes |
580 | | -
|
581 | | - We cannot check this at runtime, as attrs sets the `alias` attribute on |
582 | | - fields, but instead we can reconstruct the source code of the class and |
583 | | - check that. Unfortunately, `inspect.getsourcelines` does not work so we |
584 | | - need to build up this source code ourself. |
585 | | -
|
586 | | - The approach taken here is: |
587 | | - 1. read every file that could contain the classes in question |
588 | | - 2. tokenize them, for a couple reasons: |
589 | | - - tokenization unlike ast parsing can be 1-1 undone |
590 | | - - tokenization allows us to get the whole class block |
591 | | - - tokenization allows us to find ``class {name}`` without prefix |
592 | | - matches |
593 | | - 3. for every exported class: |
594 | | - 1. find the file |
595 | | - 2. isolate the class block |
596 | | - 3. undo tokenization |
597 | | - 4. find the string ``alias="{what it should be}"`` |
598 | | - """ |
599 | | - files = [] |
600 | | - |
601 | | - parent = (Path(inspect.getfile(trio)) / "..").resolve() |
602 | | - for path in parent.glob("**/*.py"): |
603 | | - if "_tests" in str(path)[len(str(parent)) :]: |
604 | | - continue |
605 | | - |
606 | | - with open(path, "rb") as f: |
607 | | - files.append(list(tokenize.tokenize(f.readline))) |
| 578 | + """Check whether we provide `alias` for all underscore prefixed attributes. |
608 | 579 |
|
| 580 | + Attrs always sets the `alias` attribute on fields, so a pytest plugin is used |
| 581 | + to monkeypatch `field()` to record whether an alias was defined in the metadata. |
| 582 | + See `_trio_check_attrs_aliases`. |
| 583 | + """ |
| 584 | + assert hasattr(attrs.field, "trio_modded") |
609 | 585 | for module in PUBLIC_MODULES: |
610 | | - for name, class_ in module.__dict__.items(): |
| 586 | + for class_ in module.__dict__.values(): |
611 | 587 | if not attrs.has(class_): |
612 | 588 | continue |
613 | 589 | if isinstance(class_, _util.NoPublicConstructor): |
614 | 590 | continue |
615 | 591 |
|
616 | | - file = None |
617 | | - start = None |
618 | | - for contents in files: |
619 | | - last_was_class = False |
620 | | - for i, token in enumerate(contents): |
621 | | - if ( |
622 | | - token.type == tokenize.NAME |
623 | | - and token.string == name |
624 | | - and last_was_class |
625 | | - ): |
626 | | - assert file is None |
627 | | - file = contents |
628 | | - start = i - 1 |
629 | | - |
630 | | - if token.type == tokenize.NAME and token.string == "class": |
631 | | - last_was_class = True |
632 | | - else: |
633 | | - last_was_class = False |
634 | | - |
635 | | - assert file is not None, f"{name}: {class_!r}" |
636 | | - assert start is not None |
637 | | - |
638 | | - count = -1 |
639 | | - end_offset = 0 |
640 | | - for end_offset, token in enumerate( # noqa: B007 |
641 | | - file[start:], |
642 | | - ): # pragma: no branch |
643 | | - if token.type == tokenize.INDENT: |
644 | | - count += 1 |
645 | | - if token.type == tokenize.DEDENT and count: |
646 | | - count -= 1 |
647 | | - elif token.type == tokenize.DEDENT: |
648 | | - break |
649 | | - |
650 | | - assert token.type == tokenize.DEDENT |
651 | | - class_source = ( |
652 | | - tokenize.untokenize(file[start : start + end_offset]) |
653 | | - .replace("\\\n", "") |
654 | | - .strip() |
655 | | - ) |
656 | | - |
657 | | - attributes = list(attrs.fields(class_)) |
658 | | - attributes = [attr for attr in attributes if attr.name.startswith("_")] |
659 | | - attributes = [attr for attr in attributes if attr.init] |
660 | | - |
661 | 592 | attributes = [ |
662 | | - # could this be improved by parsing AST? yes. this is simpler though. |
663 | 593 | attr |
664 | | - for attr in attributes |
665 | | - if f'alias="{attr.alias}"' not in class_source |
| 594 | + for attr in attrs.fields(class_) |
| 595 | + if attr.name.startswith("_") |
| 596 | + if attr.init |
| 597 | + if "trio_test_has_alias" not in attr.metadata |
666 | 598 | ] |
667 | 599 |
|
668 | 600 | assert attributes == [], class_ |
0 commit comments