diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 000000000..b1dd481db --- /dev/null +++ b/.coveragerc @@ -0,0 +1,5 @@ +[report] +exclude_lines = + @abstract + if TYPE_CHECKING: + pragma: no cover diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml deleted file mode 100644 index 7c528307b..000000000 --- a/.github/workflows/codeql.yml +++ /dev/null @@ -1,31 +0,0 @@ -jobs: - analyze: - name: Analyze - runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} - permissions: - actions: read - contents: read - security-events: write - strategy: - fail-fast: false - matrix: - language: [ 'python' ] - steps: - - name: Checkout repository - uses: actions/checkout@v3 - - name: Initialize CodeQL - uses: github/codeql-action/init@v2 - with: - languages: ${{ matrix.language }} - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 - with: - category: "/language:${{matrix.language}}" -name: "CodeQL" -on: - push: - branches: [ "master" ] - pull_request: - branches: [ "master" ] - schedule: - - cron: 30 1 * * 6 diff --git a/.github/workflows/pypi.yml b/.github/workflows/pypi.yml index 43521cf84..321091c14 100644 --- a/.github/workflows/pypi.yml +++ b/.github/workflows/pypi.yml @@ -6,11 +6,12 @@ jobs: id-token: write runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v5 with: - python-version: '3.x' + cache: pip + python-version: 3.x - name: Install dependencies run: | python -m pip install --upgrade pip diff --git a/.github/workflows/scorecards-analysis.yml b/.github/workflows/scorecards-analysis.yml index 0c1f0c645..4b9d35558 100644 --- a/.github/workflows/scorecards-analysis.yml +++ b/.github/workflows/scorecards-analysis.yml @@ -1,6 +1,6 @@ jobs: analysis: - name: Scorecards analysis + name: Scorecard analysis permissions: id-token: write security-events: write @@ -23,14 +23,14 @@ jobs: path: results.sarif retention-days: 5 - name: Upload to code-scanning - uses: github/codeql-action/upload-sarif@1b1aada464948af03b950897e5eb522f92603cc2 # v3.24.9 + uses: github/codeql-action/upload-sarif@v3 with: sarif_file: results.sarif -name: Scorecards supply-chain security +name: Scorecard supply-chain security on: branch_protection_rule: push: branches: [ master ] schedule: - - cron: 30 1 * * 6 + - cron: 0 15 * * 1 permissions: read-all diff --git a/praw/models/auth.py b/praw/models/auth.py index ab593cd04..2fd2f1012 100644 --- a/praw/models/auth.py +++ b/praw/models/auth.py @@ -58,7 +58,7 @@ def authorize(self, code: str) -> str | None: return authorizer.refresh_token @_deprecate_args("access_token", "expires_in", "scope") - def implicit(self, *, access_token: str, expires_in: int, scope: str) -> None: + def implicit(self, *, access_token: str, expires_in: int, scope: str): """Set the active authorization to be an implicit authorization. :param access_token: The access_token obtained from Reddit's callback. diff --git a/praw/models/comment_forest.py b/praw/models/comment_forest.py index 93a917798..4a6fec157 100644 --- a/praw/models/comment_forest.py +++ b/praw/models/comment_forest.py @@ -20,28 +20,6 @@ class CommentForest: """ - @staticmethod - def _gather_more_comments( - tree: list[praw.models.MoreComments], - *, - parent_tree: list[praw.models.MoreComments] | None = None, - ) -> list[MoreComments]: - """Return a list of :class:`.MoreComments` objects obtained from tree.""" - more_comments = [] - queue = [(None, x) for x in tree] - while queue: - parent, comment = queue.pop(0) - if isinstance(comment, MoreComments): - heappush(more_comments, comment) - if parent: - comment._remove_from = parent.replies._comments - else: - comment._remove_from = parent_tree or tree - else: - for item in comment.replies: - queue.append((comment, item)) - return more_comments - def __getitem__(self, index: int) -> praw.models.Comment: """Return the comment at position ``index`` in the list. @@ -80,11 +58,6 @@ def _insert_comment(self, comment: praw.models.Comment): parent = self._submission._comments_by_id[comment.parent_id] parent.replies._comments.append(comment) - def _update(self, comments: list[praw.models.Comment]): - self._comments = comments - for comment in comments: - comment.submission = self._submission - def list( # noqa: A003 self, ) -> list[praw.models.Comment | praw.models.MoreComments]: @@ -103,6 +76,28 @@ def list( # noqa: A003 queue.extend(comment.replies) return comments + @staticmethod + def _gather_more_comments( + tree: list[praw.models.MoreComments], + *, + parent_tree: list[praw.models.MoreComments] | None = None, + ) -> list[MoreComments]: + """Return a list of :class:`.MoreComments` objects obtained from tree.""" + more_comments = [] + queue = [(None, x) for x in tree] + while queue: + parent, comment = queue.pop(0) + if isinstance(comment, MoreComments): + heappush(more_comments, comment) + if parent: + comment._remove_from = parent.replies._comments + else: + comment._remove_from = parent_tree or tree + else: + for item in comment.replies: + queue.append((comment, item)) + return more_comments + def __init__( self, submission: praw.models.Submission, @@ -119,6 +114,11 @@ def __init__( self._comments = comments self._submission = submission + def _update(self, comments: list[praw.models.Comment]): + self._comments = comments + for comment in comments: + comment.submission = self._submission + @_deprecate_args("limit", "threshold") def replace_more( self, *, limit: int | None = 32, threshold: int = 0 diff --git a/praw/models/listing/listing.py b/praw/models/listing/listing.py index 2b7bc6efb..9ea4929fa 100644 --- a/praw/models/listing/listing.py +++ b/praw/models/listing/listing.py @@ -21,7 +21,7 @@ def __len__(self) -> int: """Return the number of items in the Listing.""" return len(getattr(self, self.CHILD_ATTRIBUTE)) - def __setattr__(self, attribute: str, value: Any) -> None: + def __setattr__(self, attribute: str, value: Any): """Objectify the ``CHILD_ATTRIBUTE`` attribute.""" if attribute == self.CHILD_ATTRIBUTE: value = self._reddit._objector.objectify(value) diff --git a/praw/models/reddit/collections.py b/praw/models/reddit/collections.py index 0bc711e9c..755d22590 100644 --- a/praw/models/reddit/collections.py +++ b/praw/models/reddit/collections.py @@ -518,7 +518,7 @@ def __len__(self) -> int: """ return len(self.link_ids) - def __setattr__(self, attribute: str, value: Any) -> None: + def __setattr__(self, attribute: str, value: Any): """Objectify author, subreddit, and sorted_links attributes.""" if attribute == "author_name": self.author = self._reddit.redditor(value) diff --git a/praw/models/reddit/poll.py b/praw/models/reddit/poll.py index 222363d40..5e1497b66 100644 --- a/praw/models/reddit/poll.py +++ b/praw/models/reddit/poll.py @@ -86,7 +86,7 @@ def user_selection(self) -> PollOption | None: return None return self.option(self._user_selection) - def __setattr__(self, attribute: str, value: Any) -> None: + def __setattr__(self, attribute: str, value: Any): """Objectify the options attribute, and save user_selection.""" if attribute == "options" and isinstance(value, list): value = [PollOption(self._reddit, option) for option in value] diff --git a/praw/reddit.py b/praw/reddit.py index e3024ea2b..7eb8f8609 100644 --- a/praw/reddit.py +++ b/praw/reddit.py @@ -95,7 +95,7 @@ def read_only(self) -> bool: return self._core == self._read_only_core @read_only.setter - def read_only(self, value: bool) -> None: + def read_only(self, value: bool): """Set or unset the use of the ReadOnlyAuthorizer. :raises: :class:`.ClientException` when attempting to unset ``read_only`` and @@ -441,6 +441,7 @@ def request(self, *args, **kwargs): def _check_for_async(self): if self.config.check_for_async: # pragma: no cover try: + # noinspection PyUnresolvedReferences shell = get_ipython().__class__.__name__ if shell == "ZMQInteractiveShell": return diff --git a/praw/util/token_manager.py b/praw/util/token_manager.py index 88f17e386..600c327dd 100644 --- a/praw/util/token_manager.py +++ b/praw/util/token_manager.py @@ -85,7 +85,7 @@ class FileTokenManager(BaseTokenManager): """ - def __init__(self, filename: str) -> None: + def __init__(self, filename: str): """Initialize a :class:`.FileTokenManager` instance. :param filename: The file the contains the refresh token. diff --git a/pytest.ini b/pytest.ini deleted file mode 100644 index 505a8a091..000000000 --- a/pytest.ini +++ /dev/null @@ -1,4 +0,0 @@ -[pytest] -testpaths = tests -filterwarnings = - ignore::DeprecationWarning diff --git a/tests/unit/models/test_util.py b/tests/unit/models/test_util.py index 6d6716c26..48d9068c7 100644 --- a/tests/unit/models/test_util.py +++ b/tests/unit/models/test_util.py @@ -105,26 +105,6 @@ def test_permissions_string__with_additional_permissions(self): class TestStream(UnitTest): - def test_stream( - self, - ): - Thing = namedtuple("Thing", ["fullname"]) - initial_things = [Thing(n) for n in reversed(range(100))] - counter = 99 - - def generate(limit, **kwargs): - nonlocal counter - counter += 1 - if counter % 2 == 0: - return initial_things - return [Thing(counter)] + initial_things[:-1] - - stream = stream_generator(generate) - seen = set() - for _ in range(400): - thing = next(stream) - assert thing not in seen - seen.add(thing) def test_comments__with_continue_after_id( self, @@ -155,3 +135,24 @@ def generate(limit, params=None, **kwargs): thing = next(stream) assert thing.fullname == expected_fullname, thing expected_fullname += 1 + + def test_stream( + self, + ): + Thing = namedtuple("Thing", ["fullname"]) + initial_things = [Thing(n) for n in reversed(range(100))] + counter = 99 + + def generate(limit, **kwargs): + nonlocal counter + counter += 1 + if counter % 2 == 0: + return initial_things + return [Thing(counter)] + initial_things[:-1] + + stream = stream_generator(generate) + seen = set() + for _ in range(400): + thing = next(stream) + assert thing not in seen + seen.add(thing) diff --git a/tools/static_word_checks.py b/tools/static_word_checks.py index 2c04f7244..ab928f5f6 100755 --- a/tools/static_word_checks.py +++ b/tools/static_word_checks.py @@ -8,10 +8,10 @@ class StaticChecker: """Run simple checks on the entire document or specific lines.""" - def __init__(self, replace: bool) -> None: + def __init__(self, replace: bool): """Initialize a :class:`.StaticChecker` instance. - :param replace: Whether or not to make replacements. + :param replace: Whether to make replacements. """ self.full_file_checks = [