diff --git a/pyproject.toml b/pyproject.toml index 60e588d..e95743a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -77,12 +77,12 @@ ignore = [ "D103", # Ignore method docstring errors in tests "PD901", # Allow `df` variable name in tests ] -"src/wpextract/dl/*" = [ - "D415", - "D103", - "D101", - "D107" -] +#"src/wpextract/dl/*" = [ +# "D415", +# "D103", +# "D101", +# "D107" +#] [tool.ruff.lint.pydocstyle] convention = "google" diff --git a/src/wpextract/__init__.py b/src/wpextract/__init__.py index b6b2633..114cd96 100644 --- a/src/wpextract/__init__.py +++ b/src/wpextract/__init__.py @@ -1,2 +1,3 @@ from wpextract.downloader import WPDownloader as WPDownloader + from .extract import WPExtractor as WPExtractor diff --git a/src/wpextract/cli/_dl.py b/src/wpextract/cli/_dl.py index 4f0b099..c2ca0b2 100644 --- a/src/wpextract/cli/_dl.py +++ b/src/wpextract/cli/_dl.py @@ -1,8 +1,8 @@ from argparse import Namespace from wpextract.cli._shared import _register_shared -from wpextract.downloader import WPDownloader from wpextract.dl.requestsession import RequestSession +from wpextract.downloader import WPDownloader from wpextract.util.args import empty_directory dl_types = ["categories", "media", "pages", "posts", "tags", "users"] diff --git a/src/wpextract/dl/__init__.py b/src/wpextract/dl/__init__.py index 429e61c..8e499e9 100644 --- a/src/wpextract/dl/__init__.py +++ b/src/wpextract/dl/__init__.py @@ -1,2 +1,2 @@ -from .requestsession import RequestSession as RequestSession from .requestsession import AuthorizationType as AuthorizationType +from .requestsession import RequestSession as RequestSession diff --git a/src/wpextract/dl/exceptions.py b/src/wpextract/dl/exceptions.py index de9bf21..7507026 100644 --- a/src/wpextract/dl/exceptions.py +++ b/src/wpextract/dl/exceptions.py @@ -1,38 +1,16 @@ -"""Copyright (c) 2018-2020 Mickaël "Kilawyn" Walter - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - - class NoWordpressApi(Exception): - """No API is available at the given URL""" + """No API is available at the given URL.""" pass class WordPressApiNotV2(Exception): - """The WordPress V2 API is not available""" + """The WordPress V2 API is not available.""" pass class NSNotFoundException(Exception): - """The specified namespace does not exist""" + """The specified namespace does not exist.""" pass diff --git a/src/wpextract/dl/exporter.py b/src/wpextract/dl/exporter.py index 79a0535..eee3e35 100644 --- a/src/wpextract/dl/exporter.py +++ b/src/wpextract/dl/exporter.py @@ -1,29 +1,8 @@ -"""Copyright (c) 2018-2020 Mickaël "Kilawyn" Walter - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - import copy import html import json -import logging import os +from typing import List from urllib import parse as urlparse from tqdm.auto import tqdm @@ -32,25 +11,24 @@ class Exporter: - """Utility functions to export data""" + """Utility functions to export data.""" - JSON = 1 - """Represents the JSON format for format choice""" CHUNK_SIZE = 2048 """The size of chunks to download large files""" @staticmethod - def download_media(session: RequestSession, media, output_folder): - """Downloads the media files based on the given URLs + def download_media( + session: RequestSession, media: List[str], output_folder: str + ) -> int: + """Downloads the media files based on the given URLs. Args: session: the request session to use media: the URLs as a list - output_folder: the path to the folder where the files are - being saved, it is assumed as existing + output_folder: the path to the folder where the files are being saved, it is assumed as existing Returns: - the number of files wrote + the number of files written """ files_number = 0 for m in tqdm(media, unit="media"): @@ -147,34 +125,27 @@ def setup_export(vlist, parameters_to_unescape): return exported_list @staticmethod - def write_file(filename, fmt, data): - """Writes content to the given file using the given format. + def write_file(filename, data): + """Writes content to the given file in JSON format. The key mapping must be a dict of keys or lists of keys to ensure proper mapping. Args: filename: the path of the file - fmt: the format of the file data: the actual data to export """ with open(filename, "w", encoding="utf-8") as f: - if fmt == Exporter.JSON: - # The JSON format is straightforward, we dump the flattened objects to JSON - json.dump(data, f, ensure_ascii=False, indent=4) - else: - raise ValueError("Unknown export format") + json.dump(data, f, ensure_ascii=False, indent=4) @staticmethod def export_posts( - posts, - fmt, - filename, + posts: List[dict], + filename: str, ): - """Exports posts in specified format to specified file + """Exports posts to the specified file. Args: posts: the posts to export - fmt: the export format (JSON or CSV) filename: filename to use Returns: @@ -185,16 +156,15 @@ def export_posts( [["title", "rendered"], ["content", "rendered"], ["excerpt", "rendered"]], ) - Exporter.write_file(filename, fmt, exported_posts) + Exporter.write_file(filename, exported_posts) return len(exported_posts) @staticmethod - def export_categories(categories, fmt, filename): - """Exports categories in specified format to specified file. + def export_categories(categories, filename): + """Exports categories to the specified file. Args: categories: the categories to export - fmt: the export format (JSON or CSV) filename: the path to the file to write Returns: @@ -205,51 +175,46 @@ def export_categories(categories, fmt, filename): [], ) - Exporter.write_file(filename, fmt, exported_categories) + Exporter.write_file(filename, exported_categories) return len(exported_categories) @staticmethod - def export_tags(tags, fmt, filename): - """Exports tags in specified format to specified file + def export_tags(tags, filename): + """Exports tags to the specified file. Args: tags: the tags to export - fmt: the export format (JSON or CSV) filename: the path to the file to write Returns: the length of the list written to the file """ exported_tags = tags # It seems that no modification will be done for this one, so no deepcopy - Exporter.write_file(filename, fmt, exported_tags) + Exporter.write_file(filename, exported_tags) return len(exported_tags) @staticmethod - def export_users(users, fmt, filename): - """Exports users in specified format to specified file. + def export_users(users, filename): + """Exports users to the specified file. Args: users: the users to export - fmt: the export format (JSON or CSV) filename: the path to the file to write Returns: the length of the list written to the file """ exported_users = users # It seems that no modification will be done for this one, so no deepcopy - Exporter.write_file(filename, fmt, exported_users) + Exporter.write_file(filename, exported_users) return len(exported_users) @staticmethod - def export_pages(pages, fmt, filename, parent_pages=None, users=None): - """Exports pages in specified format to specified file. + def export_pages(pages, filename): + """Exports pages to the specified file. Args: pages: the pages to export - fmt: the export format (JSON or CSV) filename: the path to the file to write - parent_pages: the list of all cached pages, to get parents - users: the list of all cached users, to get users Returns: the length of the list written to the file @@ -264,18 +229,16 @@ def export_pages(pages, fmt, filename, parent_pages=None, users=None): ], ) - Exporter.write_file(filename, fmt, exported_pages) + Exporter.write_file(filename, exported_pages) return len(exported_pages) @staticmethod - def export_media(media, fmt, filename, users=None): - """Exports media in specified format to specified file. + def export_media(media, filename): + """Exports media to the specified file. Args: media: the media to export - fmt: the export format (JSON or CSV) filename: file to export to - users: a list of users to associate them with author ids Returns: the length of the list written to the file @@ -290,38 +253,17 @@ def export_media(media, fmt, filename, users=None): ], ) - Exporter.write_file(filename, fmt, exported_media) + Exporter.write_file(filename, exported_media) return len(exported_media) - @staticmethod - def export_namespaces(namespaces, fmt, filename): - """**NOT IMPLEMENTED** Exports namespaces in specified format to specified file. - - Args: - namespaces: the namespaces to export - fmt: the export format (JSON or CSV) - filename: file to export to - - Returns: - the length of the list written to the file - """ - logging.info("Namespaces export not available yet") - return 0 - # FIXME to be refactored @staticmethod - def export_comments_interactive( - comments, fmt, filename, parent_posts=None, users=None - ): - """Exports comments in specified format to specified file. + def export_comments_interactive(comments, filename): + """Exports comments to the specified file. Args: comments: the comments to export - fmt: the export format (JSON or CSV) filename: the path to the file to write - parent_posts: the list of all cached posts, to get parent - posts (not used yet because this could be too verbose) - users: the list of all cached users, to get users Returns: the length of the list written to the file @@ -331,5 +273,5 @@ def export_comments_interactive( [["content", "rendered"]], ) - Exporter.write_file(filename, fmt, exported_comments) + Exporter.write_file(filename, exported_comments) return len(exported_comments) diff --git a/src/wpextract/dl/requestsession.py b/src/wpextract/dl/requestsession.py index 906002e..c38ca41 100644 --- a/src/wpextract/dl/requestsession.py +++ b/src/wpextract/dl/requestsession.py @@ -1,24 +1,3 @@ -"""Copyright (c) 2018-2020 Mickaël "Kilawyn" Walter - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - import logging import random import time @@ -34,54 +13,74 @@ class ConnectionCouldNotResolve(Exception): + """The remote host could not be resolved.""" pass class ConnectionReset(Exception): + """The connection was reset during the request.""" pass class ConnectionRefused(Exception): + """The connection was refused by the server.""" pass class ConnectionTimeout(Exception): + """A connection timeout occurred.""" pass class HTTPError400(Exception): + """HTTP Bad Request. + + See Also: + HTTPErrorInvalidPage for a special case of this error. + """ pass class HTTPErrorInvalidPage(Exception): + """Special case of HTTP 400 if the error is caused by a nonexistent page. + + This indicates the last page has been passed and all items have been retrieved. + """ pass class HTTPError401(Exception): + """HTTP Unauthorized.""" pass class HTTPError403(Exception): + """HTTP Forbidden.""" pass class HTTPError404(Exception): + """HTTP Not Found.""" pass class HTTPError500(Exception): + """HTTP Internal Server Error.""" pass class HTTPError502(Exception): + """HTTP Bad Gateway.""" pass class HTTPError(Exception): + """A generic HTTP error with an unexpected code.""" pass class HTTPTooManyRedirects(Exception): + """Raised if the number of allowed redirects exceeds the configured maximum value.""" pass @@ -149,6 +148,8 @@ def _handle_status(url, status_code, n_tries=None): class RequestWait: + """Manages waiting between requests.""" + def __init__(self, wait: float = None, random_wait: bool = False): """Create a new waiting instance. @@ -175,8 +176,7 @@ def wait(self): class RequestSession: - """Wrapper to handle the requests library with session support""" - + """Manages HTTP requests and their behaviour.""" def __init__( self, proxy: str = None, @@ -189,7 +189,7 @@ def __init__( backoff_factor: float = 0.1, max_redirects: int = 20, ): - """Creates a new RequestSession instance + """Create a new request session. Args: proxy: a dict containing a proxy server string for HTTP and/or HTTPS connection @@ -290,7 +290,7 @@ def do_request(self, method, url, data=None, stream=False): return response def set_cookies(self, cookies): - """Sets new cookies from a string""" + """Sets new cookies from a string.""" c = SimpleCookie() c.load(cookies) for key, m in c.items(): diff --git a/src/wpextract/dl/utils.py b/src/wpextract/dl/utils.py index fef0d27..eb09eaf 100644 --- a/src/wpextract/dl/utils.py +++ b/src/wpextract/dl/utils.py @@ -1,24 +1,3 @@ -"""Copyright (c) 2018-2020 Mickaël "Kilawyn" Walter - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - import json from urllib.parse import urlsplit, urlunsplit @@ -57,6 +36,15 @@ def url_path_join(*parts): def first(sequence, default=""): + """Return the first element of an iterable sequence or a default value. + + Args: + sequence: an iterable sequence. + default: the value to return if the sequence is empty. + + Returns: + The first element of an iterable sequence or a default value. + """ return next((x for x in sequence if x), default) diff --git a/src/wpextract/dl/wpapi.py b/src/wpextract/dl/wpapi.py index 074cc4b..65957c5 100644 --- a/src/wpextract/dl/wpapi.py +++ b/src/wpextract/dl/wpapi.py @@ -1,24 +1,3 @@ -"""Copyright (c) 2018-2020 Mickaël "Kilawyn" Walter - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - import copy import math from json.decoder import JSONDecodeError @@ -44,7 +23,7 @@ class WPApi: - """Queries the WordPress API to retrieve information""" + """Queries the WordPress API to retrieve information.""" # Object types POST = 0 @@ -74,7 +53,7 @@ class WPApi: """Constant representing all types""" def __init__(self, target, api_path="wp-json/", session=None, search_terms=None): - """Creates a new instance of WPApi + """Creates a new instance of WPApi. Args: target: the target of the scan @@ -106,11 +85,11 @@ def __init__(self, target, api_path="wp-json/", session=None, search_terms=None) self.s = RequestSession() def get_orphans_comments(self): - """Returns the list of comments for which a post hasn't been found""" + """Returns the list of comments for which a post hasn't been found.""" return self.orphan_comments def get_basic_info(self): - """Collects and stores basic information about the target""" + """Collects and stores basic information about the target.""" rest_url = url_path_join(self.url, self.api_path) if self.basic_info is not None: return self.basic_info @@ -245,7 +224,7 @@ def crawl_pages( return (entries, total_entries) def crawl_single_page(self, url): - """Crawls a single URL""" + """Crawls a single URL.""" content = None rest_url = url_path_join(self.url, self.api_path, url) try: @@ -264,7 +243,7 @@ def crawl_single_page(self, url): return content def get_from_cache(self, cache, start=None, num=None, force=False): - """Tries to fetch data from the given cache, also verifies first if WP-JSON is supported""" + """Tries to fetch data from the given cache, also verifies first if WP-JSON is supported.""" if self.has_v2 is None: self.get_basic_info() if not self.has_v2: @@ -331,7 +310,7 @@ def update_cache(self, cache, values, total_entries, start=None, num=None): # n return cache def get_comments(self, start=None, num=None, force=False): - """Retrieves all comments""" + """Retrieves all comments.""" comments = self.get_from_cache(self.comments, start, num, force) if comments is not None: return comments @@ -343,7 +322,7 @@ def get_comments(self, start=None, num=None, force=False): return comments def get_posts(self, comments=False, start=None, num=None, force=False): - """Retrieves all posts or the specified ones""" + """Retrieves all posts or the specified ones.""" if self.has_v2 is None: self.get_basic_info() if not self.has_v2: @@ -388,7 +367,7 @@ def get_posts(self, comments=False, start=None, num=None, force=False): return return_posts def get_tags(self, start=None, num=None, force=False): - """Retrieves all tags""" + """Retrieves all tags.""" tags = self.get_from_cache(self.tags, start, num, force) if tags is not None: return tags @@ -398,7 +377,7 @@ def get_tags(self, start=None, num=None, force=False): return tags def get_categories(self, start=None, num=None, force=False): - """Retrieves all categories or the specified ones""" + """Retrieves all categories or the specified ones.""" categories = self.get_from_cache(self.categories, start, num, force) if categories is not None: return categories @@ -412,7 +391,7 @@ def get_categories(self, start=None, num=None, force=False): return categories def get_users(self, start=None, num=None, force=False): - """Retrieves all users or the specified ones""" + """Retrieves all users or the specified ones.""" users = self.get_from_cache(self.users, start, num, force) if users is not None: return users @@ -424,7 +403,7 @@ def get_users(self, start=None, num=None, force=False): return users def get_media(self, start=None, num=None, force=False): - """Retrieves all media objects""" + """Retrieves all media objects.""" media = self.get_from_cache(self.media, start, num, force) if media is not None: return media @@ -436,7 +415,7 @@ def get_media(self, start=None, num=None, force=False): return media def get_media_urls(self, ids, cache=True): - """Retrieves the media download URLs for specified IDs or all or from cache""" + """Retrieves the media download URLs for specified IDs or all or from cache.""" media = [] if ids == "all": media = self.get_media(force=(not cache)) @@ -469,7 +448,7 @@ def get_media_urls(self, ids, cache=True): return urls, slugs def get_pages(self, start=None, num=None, force=False): - """Retrieves all pages""" + """Retrieves all pages.""" pages = self.get_from_cache(self.pages, start, num, force) if pages is not None: return pages @@ -481,7 +460,7 @@ def get_pages(self, start=None, num=None, force=False): return pages def get_namespaces(self, start=None, num=None, force=False): - """Retrieves an array of namespaces""" + """Retrieves an array of namespaces.""" if self.has_v2 is None or force: self.get_basic_info() if "namespaces" in self.basic_info.keys(): @@ -496,7 +475,7 @@ def get_namespaces(self, start=None, num=None, force=False): return [] def get_routes(self): - """Retrieves an array of routes""" + """Retrieves an array of routes.""" if self.has_v2 is None: self.get_basic_info() if "routes" in self.basic_info.keys(): diff --git a/src/wpextract/downloader.py b/src/wpextract/downloader.py index 76d66d7..2e1ce62 100644 --- a/src/wpextract/downloader.py +++ b/src/wpextract/downloader.py @@ -80,7 +80,7 @@ def download_media_files(self, session: RequestSession, dest: str): print(f"Downloaded {number_dl} media files") def _get_fetch_or_list_type(self, obj_type, plural=False): - """Returns a dict containing all necessary metadata about the obj_type to list and fetch data + """Returns a dict containing all necessary metadata about the obj_type to list and fetch data. Args: obj_type: the type of the object @@ -109,9 +109,6 @@ def _get_fetch_or_list_type(self, obj_type, plural=False): elif obj_type == WPApi.MEDIA: export_func = Exporter.export_media obj_name = "Media" - elif obj_type == WPApi.NAMESPACE: - export_func = Exporter.export_namespaces - obj_name = "Namespaces" if plural else "Namespace" return { "export_func": export_func, @@ -154,4 +151,4 @@ def export_decorator( # noqa: D102 filename = json_prefix + "-" + filename json_file = json_path / filename - export_func(values, Exporter.JSON, json_file, **kwargs) + export_func(values, json_file, **kwargs) diff --git a/tests/dl/conftest.py b/tests/dl/conftest.py index 097149a..47d9c7c 100644 --- a/tests/dl/conftest.py +++ b/tests/dl/conftest.py @@ -4,7 +4,7 @@ @pytest.fixture() def mock_request_session(mocker): - mock_session_cls = mocker.patch("wpextract.dl.downloader.RequestSession") + mock_session_cls = mocker.patch("wpextract.downloader.RequestSession") mock_session_cls = mock_session_cls.return_value return mock_session_cls diff --git a/tests/dl/test_downloader.py b/tests/dl/test_downloader.py index 5a8fdfc..83626d6 100644 --- a/tests/dl/test_downloader.py +++ b/tests/dl/test_downloader.py @@ -1,8 +1,5 @@ import pytest - -# from dl.conftest import mock_request_session from wpextract import WPDownloader -from wpextract.dl.exporter import Exporter from wpextract.dl.requestsession import ConnectionRefused from wpextract.dl.wpapi import WPApi @@ -98,6 +95,5 @@ def test_prefix(datadir, mocker, mock_request_session, prefix, expected_name): exporter.assert_called_once_with( _fake_api_return(), - Exporter.JSON, datadir / expected_name, )