badfish 1.0.6__tar.gz → 1.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {badfish-1.0.6 → badfish-1.1.0}/PKG-INFO +4 -4
- {badfish-1.0.6 → badfish-1.1.0}/README.md +0 -1
- badfish-1.1.0/pyproject.toml +13 -0
- {badfish-1.0.6 → badfish-1.1.0}/setup.cfg +4 -3
- badfish-1.1.0/setup.py +18 -0
- badfish-1.1.0/src/badfish/__init__.py +1 -0
- {badfish-1.0.6 → badfish-1.1.0}/src/badfish/helpers/http_client.py +2 -2
- {badfish-1.0.6 → badfish-1.1.0}/src/badfish/main.py +11 -7
- {badfish-1.0.6 → badfish-1.1.0}/src/badfish.egg-info/PKG-INFO +4 -4
- {badfish-1.0.6 → badfish-1.1.0}/src/badfish.egg-info/SOURCES.txt +1 -1
- {badfish-1.0.6 → badfish-1.1.0}/src/badfish.egg-info/requires.txt +2 -1
- badfish-1.1.0/tests/test_async_loop.py +60 -0
- {badfish-1.0.6 → badfish-1.1.0}/tests/test_base.py +2 -2
- {badfish-1.0.6 → badfish-1.1.0}/tests/test_context_manager.py +10 -10
- {badfish-1.0.6 → badfish-1.1.0}/tests/test_custom_interfaces.py +1 -1
- {badfish-1.0.6 → badfish-1.1.0}/tests/test_execution.py +6 -6
- {badfish-1.0.6 → badfish-1.1.0}/tests/test_firmware_inventory.py +1 -1
- {badfish-1.0.6 → badfish-1.1.0}/tests/test_hosts_file.py +4 -4
- {badfish-1.0.6 → badfish-1.1.0}/tests/test_http_client.py +2 -2
- {badfish-1.0.6 → badfish-1.1.0}/tests/test_job_queue.py +2 -2
- {badfish-1.0.6 → badfish-1.1.0}/tests/test_logger.py +1 -1
- {badfish-1.0.6 → badfish-1.1.0}/tests/test_ls_interfaces.py +4 -4
- {badfish-1.0.6 → badfish-1.1.0}/tests/test_ls_memory.py +3 -3
- {badfish-1.0.6 → badfish-1.1.0}/tests/test_ls_processors.py +3 -3
- {badfish-1.0.6 → badfish-1.1.0}/tests/test_ls_serial.py +4 -4
- {badfish-1.0.6 → badfish-1.1.0}/tests/test_nic_attributes.py +4 -4
- {badfish-1.0.6 → badfish-1.1.0}/tests/test_power.py +2 -2
- {badfish-1.0.6 → badfish-1.1.0}/tests/test_power_consumed_watts.py +1 -1
- {badfish-1.0.6 → badfish-1.1.0}/tests/test_reboot_only.py +1 -1
- {badfish-1.0.6 → badfish-1.1.0}/tests/test_scp.py +2 -2
- badfish-1.0.6/pyproject.toml +0 -6
- badfish-1.0.6/setup.py +0 -5
- badfish-1.0.6/src/badfish/__init__.py +0 -0
- badfish-1.0.6/src/badfish/helpers/async_lru.py +0 -215
- {badfish-1.0.6 → badfish-1.1.0}/LICENSE +0 -0
- {badfish-1.0.6 → badfish-1.1.0}/src/badfish/config.py +0 -0
- {badfish-1.0.6 → badfish-1.1.0}/src/badfish/helpers/__init__.py +0 -0
- {badfish-1.0.6 → badfish-1.1.0}/src/badfish/helpers/exceptions.py +0 -0
- {badfish-1.0.6 → badfish-1.1.0}/src/badfish/helpers/logger.py +0 -0
- {badfish-1.0.6 → badfish-1.1.0}/src/badfish/helpers/parser.py +0 -0
- {badfish-1.0.6 → badfish-1.1.0}/src/badfish.egg-info/dependency_links.txt +0 -0
- {badfish-1.0.6 → badfish-1.1.0}/src/badfish.egg-info/entry_points.txt +0 -0
- {badfish-1.0.6 → badfish-1.1.0}/src/badfish.egg-info/top_level.txt +0 -0
- {badfish-1.0.6 → badfish-1.1.0}/src/badfish.egg-info/zip-safe +0 -0
- {badfish-1.0.6 → badfish-1.1.0}/tests/test_bios_attributes.py +0 -0
- {badfish-1.0.6 → badfish-1.1.0}/tests/test_bios_password.py +0 -0
- {badfish-1.0.6 → badfish-1.1.0}/tests/test_boot_to.py +0 -0
- {badfish-1.0.6 → badfish-1.1.0}/tests/test_boot_to_mac.py +0 -0
- {badfish-1.0.6 → badfish-1.1.0}/tests/test_boot_to_type.py +0 -0
- {badfish-1.0.6 → badfish-1.1.0}/tests/test_change_boot.py +0 -0
- {badfish-1.0.6 → badfish-1.1.0}/tests/test_check_boot.py +0 -0
- {badfish-1.0.6 → badfish-1.1.0}/tests/test_ls_gpu.py +0 -0
- {badfish-1.0.6 → badfish-1.1.0}/tests/test_main_coverage.py +0 -0
- {badfish-1.0.6 → badfish-1.1.0}/tests/test_next_boot_pxe.py +0 -0
- {badfish-1.0.6 → badfish-1.1.0}/tests/test_reset_bios.py +0 -0
- {badfish-1.0.6 → badfish-1.1.0}/tests/test_reset_bmc.py +0 -0
- {badfish-1.0.6 → badfish-1.1.0}/tests/test_reset_idrac.py +0 -0
- {badfish-1.0.6 → badfish-1.1.0}/tests/test_screenshot.py +0 -0
- {badfish-1.0.6 → badfish-1.1.0}/tests/test_sriov_mode.py +0 -0
- {badfish-1.0.6 → badfish-1.1.0}/tests/test_virtual_media.py +0 -0
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: badfish
|
|
3
|
-
Version: 1.0
|
|
3
|
+
Version: 1.1.0
|
|
4
4
|
Summary: Badfish is a Redfish-based API tool for managing bare-metal systems via the Redfish API
|
|
5
5
|
Home-page: https://github.com/redhat-performance/badfish
|
|
6
6
|
Author: Gonzalo Rafuls
|
|
7
7
|
Author-email: gonza@redhat.com
|
|
8
|
-
License:
|
|
8
|
+
License: GPL-3.0-or-later
|
|
9
9
|
Project-URL: Bug Tracker, https://github.com/redhat-performance/badfish/issues
|
|
10
10
|
Project-URL: Documentation, https://github.com/redhat-performance/badfish/blob/master/README.md
|
|
11
11
|
Project-URL: Source Code, https://github.com/redhat-performance/badfish
|
|
@@ -24,8 +24,9 @@ Requires-Python: >=3.10
|
|
|
24
24
|
Description-Content-Type: text/markdown
|
|
25
25
|
License-File: LICENSE
|
|
26
26
|
Requires-Dist: pyyaml>=3.10
|
|
27
|
-
Requires-Dist: aiohttp>=3.
|
|
27
|
+
Requires-Dist: aiohttp>=3.10
|
|
28
28
|
Requires-Dist: setuptools>=39.0
|
|
29
|
+
Requires-Dist: async-lru>=2.0
|
|
29
30
|
Dynamic: license-file
|
|
30
31
|
|
|
31
32
|
<p align="center">
|
|
@@ -753,7 +754,6 @@ With rack, ULocation and blade being optional in a hierarchical fashion otherwis
|
|
|
753
754
|
|
|
754
755
|
Please refer to our contributing [guide](CONTRIBUTING.md).
|
|
755
756
|
|
|
756
|
-
|
|
757
757
|
* Here is some useful documentation
|
|
758
758
|
- [Creating a pull request](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request)
|
|
759
759
|
- [Keeping a cloned fork up to date](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/syncing-a-fork)
|
|
@@ -723,7 +723,6 @@ With rack, ULocation and blade being optional in a hierarchical fashion otherwis
|
|
|
723
723
|
|
|
724
724
|
Please refer to our contributing [guide](CONTRIBUTING.md).
|
|
725
725
|
|
|
726
|
-
|
|
727
726
|
* Here is some useful documentation
|
|
728
727
|
- [Creating a pull request](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request)
|
|
729
728
|
- [Keeping a cloned fork up to date](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/syncing-a-fork)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=39", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[tool.black]
|
|
6
|
+
line-length = 120
|
|
7
|
+
|
|
8
|
+
[tool.semantic_release]
|
|
9
|
+
version_variables = [
|
|
10
|
+
"src/badfish/__init__.py:__version__"
|
|
11
|
+
]
|
|
12
|
+
branch = "master"
|
|
13
|
+
build_command = "pip install build && python -m build"
|
|
@@ -5,7 +5,7 @@ author_email = gonza@redhat.com
|
|
|
5
5
|
description = Badfish is a Redfish-based API tool for managing bare-metal systems via the Redfish API
|
|
6
6
|
long_description = file: README.md
|
|
7
7
|
long_description_content_type = text/markdown
|
|
8
|
-
license =
|
|
8
|
+
license = GPL-3.0-or-later
|
|
9
9
|
license_file = LICENSE
|
|
10
10
|
platforms = any
|
|
11
11
|
url = https://github.com/redhat-performance/badfish
|
|
@@ -30,10 +30,11 @@ packages = find:
|
|
|
30
30
|
python_requires = >=3.10
|
|
31
31
|
install_requires =
|
|
32
32
|
pyyaml>=3.10
|
|
33
|
-
aiohttp>=3.
|
|
33
|
+
aiohttp>=3.10
|
|
34
34
|
setuptools>=39.0
|
|
35
|
+
async-lru>=2.0
|
|
35
36
|
package_dir =
|
|
36
|
-
=src
|
|
37
|
+
= src
|
|
37
38
|
zip_safe = True
|
|
38
39
|
|
|
39
40
|
[options.packages.find]
|
badfish-1.1.0/setup.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import setuptools
|
|
2
|
+
import re
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
here = os.path.abspath(os.path.dirname(__file__))
|
|
6
|
+
version_file = os.path.join(here, "src", "badfish", "__init__.py")
|
|
7
|
+
|
|
8
|
+
with open(version_file, "r", encoding="utf-8") as f:
|
|
9
|
+
content = f.read()
|
|
10
|
+
match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]", content, re.M)
|
|
11
|
+
if match:
|
|
12
|
+
current_version = match.group(1)
|
|
13
|
+
else:
|
|
14
|
+
raise RuntimeError("Unable to find version string in src/badfish/__init__.py")
|
|
15
|
+
|
|
16
|
+
setuptools.setup(
|
|
17
|
+
version=current_version
|
|
18
|
+
)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "1.1.0"
|
|
@@ -4,8 +4,8 @@ from typing import Any, Dict, Optional
|
|
|
4
4
|
|
|
5
5
|
import aiohttp
|
|
6
6
|
|
|
7
|
-
from
|
|
8
|
-
from
|
|
7
|
+
from async_lru import alru_cache
|
|
8
|
+
from badfish.helpers.exceptions import BadfishException
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
class HTTPClient:
|
|
@@ -13,11 +13,11 @@ import yaml
|
|
|
13
13
|
import tempfile
|
|
14
14
|
from urllib.parse import urlparse
|
|
15
15
|
|
|
16
|
-
from
|
|
17
|
-
from
|
|
18
|
-
from
|
|
19
|
-
from
|
|
20
|
-
from
|
|
16
|
+
from badfish.helpers import get_now
|
|
17
|
+
from badfish.helpers.parser import parse_arguments
|
|
18
|
+
from badfish.helpers.logger import BadfishLogger
|
|
19
|
+
from badfish.helpers.http_client import HTTPClient
|
|
20
|
+
from badfish.helpers.exceptions import BadfishException
|
|
21
21
|
|
|
22
22
|
from logging import (
|
|
23
23
|
DEBUG,
|
|
@@ -393,7 +393,7 @@ class Badfish:
|
|
|
393
393
|
|
|
394
394
|
_uri = "%s%s" % (self.host_uri, session_uri)
|
|
395
395
|
check_response = await self.http_client.get_request(_uri, _continue=True, _get_token=True)
|
|
396
|
-
if check_response is None:
|
|
396
|
+
if check_response is None or check_response.status != 200:
|
|
397
397
|
session_uri = "/redfish/v1/SessionService/Sessions"
|
|
398
398
|
|
|
399
399
|
return session_uri
|
|
@@ -2674,7 +2674,11 @@ def main(argv=None):
|
|
|
2674
2674
|
output = _args["output"]
|
|
2675
2675
|
bfl = BadfishLogger(_args["verbose"], multi_host, _args["log"], output)
|
|
2676
2676
|
|
|
2677
|
-
|
|
2677
|
+
try:
|
|
2678
|
+
loop = asyncio.get_event_loop()
|
|
2679
|
+
except RuntimeError:
|
|
2680
|
+
loop = asyncio.new_event_loop()
|
|
2681
|
+
asyncio.set_event_loop(loop)
|
|
2678
2682
|
tasks = []
|
|
2679
2683
|
host_order = {}
|
|
2680
2684
|
if host_list:
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: badfish
|
|
3
|
-
Version: 1.0
|
|
3
|
+
Version: 1.1.0
|
|
4
4
|
Summary: Badfish is a Redfish-based API tool for managing bare-metal systems via the Redfish API
|
|
5
5
|
Home-page: https://github.com/redhat-performance/badfish
|
|
6
6
|
Author: Gonzalo Rafuls
|
|
7
7
|
Author-email: gonza@redhat.com
|
|
8
|
-
License:
|
|
8
|
+
License: GPL-3.0-or-later
|
|
9
9
|
Project-URL: Bug Tracker, https://github.com/redhat-performance/badfish/issues
|
|
10
10
|
Project-URL: Documentation, https://github.com/redhat-performance/badfish/blob/master/README.md
|
|
11
11
|
Project-URL: Source Code, https://github.com/redhat-performance/badfish
|
|
@@ -24,8 +24,9 @@ Requires-Python: >=3.10
|
|
|
24
24
|
Description-Content-Type: text/markdown
|
|
25
25
|
License-File: LICENSE
|
|
26
26
|
Requires-Dist: pyyaml>=3.10
|
|
27
|
-
Requires-Dist: aiohttp>=3.
|
|
27
|
+
Requires-Dist: aiohttp>=3.10
|
|
28
28
|
Requires-Dist: setuptools>=39.0
|
|
29
|
+
Requires-Dist: async-lru>=2.0
|
|
29
30
|
Dynamic: license-file
|
|
30
31
|
|
|
31
32
|
<p align="center">
|
|
@@ -753,7 +754,6 @@ With rack, ULocation and blade being optional in a hierarchical fashion otherwis
|
|
|
753
754
|
|
|
754
755
|
Please refer to our contributing [guide](CONTRIBUTING.md).
|
|
755
756
|
|
|
756
|
-
|
|
757
757
|
* Here is some useful documentation
|
|
758
758
|
- [Creating a pull request](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request)
|
|
759
759
|
- [Keeping a cloned fork up to date](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/syncing-a-fork)
|
|
@@ -14,11 +14,11 @@ src/badfish.egg-info/requires.txt
|
|
|
14
14
|
src/badfish.egg-info/top_level.txt
|
|
15
15
|
src/badfish.egg-info/zip-safe
|
|
16
16
|
src/badfish/helpers/__init__.py
|
|
17
|
-
src/badfish/helpers/async_lru.py
|
|
18
17
|
src/badfish/helpers/exceptions.py
|
|
19
18
|
src/badfish/helpers/http_client.py
|
|
20
19
|
src/badfish/helpers/logger.py
|
|
21
20
|
src/badfish/helpers/parser.py
|
|
21
|
+
tests/test_async_loop.py
|
|
22
22
|
tests/test_base.py
|
|
23
23
|
tests/test_bios_attributes.py
|
|
24
24
|
tests/test_bios_password.py
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
from unittest.mock import patch, MagicMock
|
|
3
|
+
from badfish.main import main
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class TestAsyncioFix(unittest.TestCase):
|
|
7
|
+
@patch('badfish.main.execute_badfish')
|
|
8
|
+
@patch('badfish.main.BadfishLogger')
|
|
9
|
+
@patch('badfish.main.parse_arguments')
|
|
10
|
+
@patch('asyncio.set_event_loop')
|
|
11
|
+
@patch('asyncio.new_event_loop')
|
|
12
|
+
@patch('asyncio.get_event_loop')
|
|
13
|
+
def test_main_handles_no_event_loop(self, mock_get_loop, mock_new_loop,
|
|
14
|
+
mock_set_loop, mock_parse_args,
|
|
15
|
+
mock_logger, mock_execute):
|
|
16
|
+
mock_get_loop.side_effect = RuntimeError("No event loop")
|
|
17
|
+
|
|
18
|
+
mock_loop_instance = MagicMock()
|
|
19
|
+
mock_new_loop.return_value = mock_loop_instance
|
|
20
|
+
mock_loop_instance.run_until_complete.return_value = ("localhost", True)
|
|
21
|
+
|
|
22
|
+
mock_parse_args.return_value = {
|
|
23
|
+
"verbose": False, "host": "localhost", "delta": None,
|
|
24
|
+
"firmware_inventory": None, "host_list": None, "log": None,
|
|
25
|
+
"output": None
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
main()
|
|
29
|
+
|
|
30
|
+
mock_get_loop.assert_called_once()
|
|
31
|
+
mock_new_loop.assert_called_once()
|
|
32
|
+
mock_set_loop.assert_called_once_with(mock_loop_instance)
|
|
33
|
+
mock_loop_instance.run_until_complete.assert_called()
|
|
34
|
+
|
|
35
|
+
@patch('badfish.main.execute_badfish')
|
|
36
|
+
@patch('badfish.main.BadfishLogger')
|
|
37
|
+
@patch('badfish.main.parse_arguments')
|
|
38
|
+
@patch('asyncio.set_event_loop')
|
|
39
|
+
@patch('asyncio.new_event_loop')
|
|
40
|
+
@patch('asyncio.get_event_loop')
|
|
41
|
+
def test_main_uses_existing_loop(self, mock_get_loop, mock_new_loop,
|
|
42
|
+
mock_set_loop, mock_parse_args,
|
|
43
|
+
mock_logger, mock_execute):
|
|
44
|
+
existing_loop = MagicMock()
|
|
45
|
+
mock_get_loop.return_value = existing_loop
|
|
46
|
+
mock_get_loop.side_effect = None
|
|
47
|
+
existing_loop.run_until_complete.return_value = ("localhost", True)
|
|
48
|
+
|
|
49
|
+
mock_parse_args.return_value = {
|
|
50
|
+
"verbose": False, "host": "localhost", "delta": None,
|
|
51
|
+
"firmware_inventory": None, "host_list": None, "log": None,
|
|
52
|
+
"output": None
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
main()
|
|
56
|
+
|
|
57
|
+
mock_get_loop.assert_called_once()
|
|
58
|
+
mock_new_loop.assert_not_called()
|
|
59
|
+
mock_set_loop.assert_not_called()
|
|
60
|
+
existing_loop.run_until_complete.assert_called()
|
|
@@ -5,8 +5,8 @@ import pytest
|
|
|
5
5
|
from aiohttp import web
|
|
6
6
|
from aiohttp.test_utils import AioHTTPTestCase
|
|
7
7
|
|
|
8
|
-
from
|
|
9
|
-
from
|
|
8
|
+
from badfish.main import main
|
|
9
|
+
from badfish.helpers.exceptions import BadfishException
|
|
10
10
|
from tests import config
|
|
11
11
|
|
|
12
12
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import pytest
|
|
2
2
|
from unittest.mock import AsyncMock, patch, MagicMock
|
|
3
|
-
from
|
|
4
|
-
from
|
|
3
|
+
from badfish.main import Badfish, badfish_factory
|
|
4
|
+
from badfish.helpers.exceptions import BadfishException
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
class MockLogger:
|
|
@@ -386,7 +386,7 @@ class TestExecuteBadfishSessionCleanup:
|
|
|
386
386
|
@pytest.mark.asyncio
|
|
387
387
|
async def test_execute_badfish_session_cleanup_success(self):
|
|
388
388
|
"""Test successful session cleanup in execute_badfish."""
|
|
389
|
-
from
|
|
389
|
+
from badfish.main import execute_badfish
|
|
390
390
|
|
|
391
391
|
logger = MockLogger()
|
|
392
392
|
args = {
|
|
@@ -451,7 +451,7 @@ class TestExecuteBadfishSessionCleanup:
|
|
|
451
451
|
"set_nic_attribute": None,
|
|
452
452
|
}
|
|
453
453
|
|
|
454
|
-
with patch("
|
|
454
|
+
with patch("badfish.main.badfish_factory") as mock_factory:
|
|
455
455
|
# Mock badfish instance
|
|
456
456
|
mock_badfish = MagicMock()
|
|
457
457
|
mock_badfish.session_id = "/redfish/v1/SessionService/Sessions/123"
|
|
@@ -469,7 +469,7 @@ class TestExecuteBadfishSessionCleanup:
|
|
|
469
469
|
@pytest.mark.asyncio
|
|
470
470
|
async def test_execute_badfish_session_cleanup_failure(self):
|
|
471
471
|
"""Test session cleanup failure in execute_badfish."""
|
|
472
|
-
from
|
|
472
|
+
from badfish.main import execute_badfish
|
|
473
473
|
|
|
474
474
|
logger = MockLogger()
|
|
475
475
|
args = {
|
|
@@ -534,7 +534,7 @@ class TestExecuteBadfishSessionCleanup:
|
|
|
534
534
|
"set_nic_attribute": None,
|
|
535
535
|
}
|
|
536
536
|
|
|
537
|
-
with patch("
|
|
537
|
+
with patch("badfish.main.badfish_factory") as mock_factory:
|
|
538
538
|
# Mock badfish instance
|
|
539
539
|
mock_badfish = MagicMock()
|
|
540
540
|
mock_badfish.session_id = "/redfish/v1/SessionService/Sessions/123"
|
|
@@ -556,7 +556,7 @@ class TestExecuteBadfishSessionCleanup:
|
|
|
556
556
|
@pytest.mark.asyncio
|
|
557
557
|
async def test_execute_badfish_no_session_cleanup(self):
|
|
558
558
|
"""Test execute_badfish when no session exists to clean up."""
|
|
559
|
-
from
|
|
559
|
+
from badfish.main import execute_badfish
|
|
560
560
|
|
|
561
561
|
logger = MockLogger()
|
|
562
562
|
args = {
|
|
@@ -621,7 +621,7 @@ class TestExecuteBadfishSessionCleanup:
|
|
|
621
621
|
"set_nic_attribute": None,
|
|
622
622
|
}
|
|
623
623
|
|
|
624
|
-
with patch("
|
|
624
|
+
with patch("badfish.main.badfish_factory") as mock_factory:
|
|
625
625
|
# Mock badfish instance with no session_id
|
|
626
626
|
mock_badfish = MagicMock()
|
|
627
627
|
mock_badfish.session_id = None
|
|
@@ -639,7 +639,7 @@ class TestExecuteBadfishSessionCleanup:
|
|
|
639
639
|
@pytest.mark.asyncio
|
|
640
640
|
async def test_execute_badfish_no_badfish_instance(self):
|
|
641
641
|
"""Test execute_badfish when badfish instance is None."""
|
|
642
|
-
from
|
|
642
|
+
from badfish.main import execute_badfish
|
|
643
643
|
|
|
644
644
|
logger = MockLogger()
|
|
645
645
|
args = {
|
|
@@ -704,7 +704,7 @@ class TestExecuteBadfishSessionCleanup:
|
|
|
704
704
|
"set_nic_attribute": None,
|
|
705
705
|
}
|
|
706
706
|
|
|
707
|
-
with patch("
|
|
707
|
+
with patch("badfish.main.badfish_factory") as mock_factory:
|
|
708
708
|
# Mock badfish_factory to raise an exception
|
|
709
709
|
mock_factory.side_effect = BadfishException("Connection failed")
|
|
710
710
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import os
|
|
2
2
|
from unittest.mock import patch
|
|
3
3
|
|
|
4
|
-
from
|
|
4
|
+
from badfish.helpers.exceptions import BadfishException
|
|
5
5
|
from tests.config import (
|
|
6
6
|
HOST_LIST_EXTRAS,
|
|
7
7
|
KEYBOARD_INTERRUPT,
|
|
@@ -30,12 +30,12 @@ def raise_badfish_exception_stub(ignore1, ignore2, ignore3, ignore4=None):
|
|
|
30
30
|
class TestSingleHostExecution(TestBase):
|
|
31
31
|
args = ["--ls-jobs"]
|
|
32
32
|
|
|
33
|
-
@patch("
|
|
33
|
+
@patch("badfish.main.execute_badfish", raise_keyb_interrupt_stub)
|
|
34
34
|
def test_single_host_keyb_interrupt(self):
|
|
35
35
|
_, err = self.badfish_call()
|
|
36
36
|
assert err == KEYBOARD_INTERRUPT
|
|
37
37
|
|
|
38
|
-
@patch("
|
|
38
|
+
@patch("badfish.main.execute_badfish", raise_badfish_exception_stub)
|
|
39
39
|
def test_single_host_badfish_exception(self):
|
|
40
40
|
_, err = self.badfish_call()
|
|
41
41
|
assert err == WRONG_BADFISH_EXECUTION
|
|
@@ -52,17 +52,17 @@ class TestHostListExecution(TestBase):
|
|
|
52
52
|
"--ls-jobs",
|
|
53
53
|
]
|
|
54
54
|
|
|
55
|
-
@patch("
|
|
55
|
+
@patch("badfish.main.execute_badfish", raise_keyb_interrupt_stub)
|
|
56
56
|
def test_host_list_keyb_interrupt(self):
|
|
57
57
|
_, err = self.badfish_call(mock_host=None)
|
|
58
58
|
assert err == KEYBOARD_INTERRUPT_HOST_LIST
|
|
59
59
|
|
|
60
|
-
@patch("
|
|
60
|
+
@patch("badfish.main.execute_badfish", raise_badfish_exception_stub)
|
|
61
61
|
def test_host_list_badfish_exception(self):
|
|
62
62
|
_, err = self.badfish_call(mock_host=None)
|
|
63
63
|
assert err == WRONG_BADFISH_EXECUTION_HOST_LIST
|
|
64
64
|
|
|
65
|
-
@patch("
|
|
65
|
+
@patch("badfish.main.execute_badfish")
|
|
66
66
|
def test_host_list_successful(self, mock_execute):
|
|
67
67
|
mock_execute.return_value = "Successful."
|
|
68
68
|
_, err = self.badfish_call(mock_host=None)
|
|
@@ -62,7 +62,7 @@ class TestFirmwareInventory(TestBase):
|
|
|
62
62
|
@patch("aiohttp.ClientSession.delete")
|
|
63
63
|
@patch("aiohttp.ClientSession.post")
|
|
64
64
|
@patch("aiohttp.ClientSession.get")
|
|
65
|
-
@patch("
|
|
65
|
+
@patch("badfish.main.Badfish.get_request")
|
|
66
66
|
def test_firmware_inventory_none_response(self, mock_get_req_call, mock_get, mock_post, mock_delete):
|
|
67
67
|
mock_get_req_call.side_effect = [
|
|
68
68
|
MockResponse(FIRMWARE_INVENTORY_RESP, 200), # Firmware inventory list
|
|
@@ -16,7 +16,7 @@ class TestHostsFile(TestBase):
|
|
|
16
16
|
def test_hosts_good(self):
|
|
17
17
|
self.args = [self.option_arg, self.mock_hosts_good_path]
|
|
18
18
|
|
|
19
|
-
with patch("
|
|
19
|
+
with patch("badfish.main.execute_badfish") as badfish_mock:
|
|
20
20
|
self.badfish_call(mock_host=None)
|
|
21
21
|
|
|
22
22
|
badfish_mock.assert_awaited()
|
|
@@ -32,7 +32,7 @@ class TestHostsFile(TestBase):
|
|
|
32
32
|
def test_hosts_non_existent(self):
|
|
33
33
|
self.args = [self.option_arg, "non/existent/file"]
|
|
34
34
|
|
|
35
|
-
with patch("
|
|
35
|
+
with patch("badfish.main.execute_badfish") as badfish_mock:
|
|
36
36
|
out, err = self.badfish_call(mock_host=None)
|
|
37
37
|
badfish_mock.assert_not_awaited()
|
|
38
38
|
|
|
@@ -44,7 +44,7 @@ class TestHostsFile(TestBase):
|
|
|
44
44
|
"""
|
|
45
45
|
self.args = [self.option_arg, self.mock_hosts_empty_path]
|
|
46
46
|
|
|
47
|
-
with patch("
|
|
47
|
+
with patch("badfish.main.execute_badfish") as badfish_mock:
|
|
48
48
|
result = self.badfish_call(mock_host=None)
|
|
49
49
|
|
|
50
50
|
badfish_mock.assert_not_awaited()
|
|
@@ -53,7 +53,7 @@ class TestHostsFile(TestBase):
|
|
|
53
53
|
def test_hosts_bad(self):
|
|
54
54
|
self.args = [self.option_arg, self.mock_hosts_garbled_path]
|
|
55
55
|
|
|
56
|
-
with patch("
|
|
56
|
+
with patch("badfish.main.execute_badfish") as badfish_mock:
|
|
57
57
|
self.badfish_call(mock_host=None)
|
|
58
58
|
|
|
59
59
|
badfish_mock.assert_awaited()
|
|
@@ -5,8 +5,8 @@ from unittest.mock import AsyncMock, MagicMock, PropertyMock, patch
|
|
|
5
5
|
import aiohttp
|
|
6
6
|
import pytest
|
|
7
7
|
|
|
8
|
-
from
|
|
9
|
-
from
|
|
8
|
+
from badfish.helpers.http_client import HTTPClient
|
|
9
|
+
from badfish.helpers.exceptions import BadfishException
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
class DummyLogger:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from unittest.mock import patch
|
|
2
2
|
|
|
3
|
-
from
|
|
3
|
+
from badfish.helpers.exceptions import BadfishException
|
|
4
4
|
from tests.config import (
|
|
5
5
|
BLANK_RESP,
|
|
6
6
|
INIT_RESP,
|
|
@@ -204,7 +204,7 @@ class TestCheckJob(TestBase):
|
|
|
204
204
|
@patch("aiohttp.ClientSession.delete")
|
|
205
205
|
@patch("aiohttp.ClientSession.post")
|
|
206
206
|
@patch("aiohttp.ClientSession.get")
|
|
207
|
-
@patch("
|
|
207
|
+
@patch("badfish.main.Badfish.get_request")
|
|
208
208
|
def test_check_job_error(self, mock_get_req_call, mock_get, mock_post, mock_delete):
|
|
209
209
|
# The check_schedule_job_status method only makes one call to get_request
|
|
210
210
|
# which should return None to simulate the error condition
|
|
@@ -2,7 +2,7 @@ import os
|
|
|
2
2
|
import tempfile
|
|
3
3
|
from logging import INFO, ERROR, DEBUG, LogRecord
|
|
4
4
|
|
|
5
|
-
from
|
|
5
|
+
from badfish.helpers.logger import BadfishHandler, BadfishLogger
|
|
6
6
|
import yaml
|
|
7
7
|
from unittest.mock import patch
|
|
8
8
|
|
|
@@ -61,8 +61,8 @@ class TestLsInterfaces(TestBase):
|
|
|
61
61
|
@patch("aiohttp.ClientSession.delete")
|
|
62
62
|
@patch("aiohttp.ClientSession.post")
|
|
63
63
|
@patch("aiohttp.ClientSession.get")
|
|
64
|
-
@patch("
|
|
65
|
-
@patch("
|
|
64
|
+
@patch("badfish.main.Badfish.check_supported_network_interfaces")
|
|
65
|
+
@patch("badfish.main.Badfish.get_ethernet_interfaces")
|
|
66
66
|
def test_ls_interfaces_ethernet(self, mock_get_ethernet, mock_check_support, mock_get, mock_post, mock_delete):
|
|
67
67
|
# Mock the support checks: NetworkAdapters not supported, EthernetInterfaces supported
|
|
68
68
|
mock_check_support.side_effect = [False, True]
|
|
@@ -94,7 +94,7 @@ class TestLsInterfaces(TestBase):
|
|
|
94
94
|
@patch("aiohttp.ClientSession.delete")
|
|
95
95
|
@patch("aiohttp.ClientSession.post")
|
|
96
96
|
@patch("aiohttp.ClientSession.get")
|
|
97
|
-
@patch("
|
|
97
|
+
@patch("badfish.main.Badfish.check_supported_network_interfaces")
|
|
98
98
|
def test_ls_interfaces_ethernet_not_supported(self, mock_check_support, mock_get, mock_post, mock_delete):
|
|
99
99
|
# Mock the support checks: both NetworkAdapters and EthernetInterfaces not supported
|
|
100
100
|
mock_check_support.side_effect = [False, False]
|
|
@@ -125,7 +125,7 @@ class TestLsInterfaces(TestBase):
|
|
|
125
125
|
@patch("aiohttp.ClientSession.delete")
|
|
126
126
|
@patch("aiohttp.ClientSession.post")
|
|
127
127
|
@patch("aiohttp.ClientSession.get")
|
|
128
|
-
@patch("
|
|
128
|
+
@patch("badfish.main.Badfish.check_supported_network_interfaces")
|
|
129
129
|
def test_ls_interfaces_none_supported(self, mock_check_support, mock_get, mock_post, mock_delete):
|
|
130
130
|
# Mock the support checks: both NetworkAdapters and EthernetInterfaces not supported
|
|
131
131
|
mock_check_support.side_effect = [False, False]
|
|
@@ -68,12 +68,12 @@ class TestLsMemory(TestBase):
|
|
|
68
68
|
@patch("aiohttp.ClientSession.delete")
|
|
69
69
|
@patch("aiohttp.ClientSession.post")
|
|
70
70
|
@patch("aiohttp.ClientSession.get")
|
|
71
|
-
@patch("
|
|
72
|
-
@patch("
|
|
71
|
+
@patch("badfish.main.Badfish.get_memory_summary")
|
|
72
|
+
@patch("badfish.main.Badfish.get_memory_details")
|
|
73
73
|
def test_ls_memory_details_not_found(
|
|
74
74
|
self, mock_get_mem_details, mock_get_mem_summary, mock_get, mock_post, mock_delete
|
|
75
75
|
):
|
|
76
|
-
from
|
|
76
|
+
from badfish.main import BadfishException
|
|
77
77
|
|
|
78
78
|
# Mock successful memory summary
|
|
79
79
|
memory_summary = {"MemoryMirroring": "System", "TotalSystemMemoryGiB": 384}
|
|
@@ -69,12 +69,12 @@ class TestLsProcessors(TestBase):
|
|
|
69
69
|
@patch("aiohttp.ClientSession.delete")
|
|
70
70
|
@patch("aiohttp.ClientSession.post")
|
|
71
71
|
@patch("aiohttp.ClientSession.get")
|
|
72
|
-
@patch("
|
|
73
|
-
@patch("
|
|
72
|
+
@patch("badfish.main.Badfish.get_processor_summary")
|
|
73
|
+
@patch("badfish.main.Badfish.get_processor_details")
|
|
74
74
|
def test_ls_processors_details_not_found(
|
|
75
75
|
self, mock_get_proc_details, mock_get_proc_summary, mock_get, mock_post, mock_delete
|
|
76
76
|
):
|
|
77
|
-
from
|
|
77
|
+
from badfish.main import BadfishException
|
|
78
78
|
|
|
79
79
|
# Mock successful processor summary (the data from PROCESSOR_SUMMARY_RESP)
|
|
80
80
|
processor_summary = {
|
|
@@ -41,9 +41,9 @@ class TestLsSerial(TestBase):
|
|
|
41
41
|
@patch("aiohttp.ClientSession.delete")
|
|
42
42
|
@patch("aiohttp.ClientSession.post")
|
|
43
43
|
@patch("aiohttp.ClientSession.get")
|
|
44
|
-
@patch("
|
|
44
|
+
@patch("badfish.main.Badfish.get_serial_summary")
|
|
45
45
|
def test_ls_serial_unsupported_root(self, mock_get_serial_summary, mock_get, mock_post, mock_delete):
|
|
46
|
-
from
|
|
46
|
+
from badfish.main import BadfishException
|
|
47
47
|
|
|
48
48
|
# Mock serial summary to raise the expected exception
|
|
49
49
|
mock_get_serial_summary.side_effect = BadfishException("Server does not support this functionality")
|
|
@@ -57,9 +57,9 @@ class TestLsSerial(TestBase):
|
|
|
57
57
|
@patch("aiohttp.ClientSession.delete")
|
|
58
58
|
@patch("aiohttp.ClientSession.post")
|
|
59
59
|
@patch("aiohttp.ClientSession.get")
|
|
60
|
-
@patch("
|
|
60
|
+
@patch("badfish.main.Badfish.get_serial_summary")
|
|
61
61
|
def test_ls_serial_unsupported_systems(self, mock_get_serial_summary, mock_get, mock_post, mock_delete):
|
|
62
|
-
from
|
|
62
|
+
from badfish.main import BadfishException
|
|
63
63
|
|
|
64
64
|
# Mock serial summary to raise the expected exception
|
|
65
65
|
mock_get_serial_summary.side_effect = BadfishException("Server does not support this functionality")
|
|
@@ -124,9 +124,9 @@ class TestGetNICAttribute(TestBase):
|
|
|
124
124
|
@patch("aiohttp.ClientSession.delete")
|
|
125
125
|
@patch("aiohttp.ClientSession.post")
|
|
126
126
|
@patch("aiohttp.ClientSession.get")
|
|
127
|
-
@patch("
|
|
127
|
+
@patch("badfish.main.Badfish.get_nic_attribute")
|
|
128
128
|
def test_get_nic_attr_list_unsupported(self, mock_get_nic_attr, mock_get, mock_post, mock_delete):
|
|
129
|
-
from
|
|
129
|
+
from badfish.main import BadfishException
|
|
130
130
|
|
|
131
131
|
# Mock get_nic_attribute to raise BadfishException with vendor unsupported message
|
|
132
132
|
mock_get_nic_attr.side_effect = BadfishException("Operation not supported by vendor.")
|
|
@@ -172,14 +172,14 @@ class TestGetNICAttribute(TestBase):
|
|
|
172
172
|
@patch("aiohttp.ClientSession.delete")
|
|
173
173
|
@patch("aiohttp.ClientSession.post")
|
|
174
174
|
@patch("aiohttp.ClientSession.get")
|
|
175
|
-
@patch("
|
|
175
|
+
@patch("badfish.main.Badfish.get_idrac_fw_version")
|
|
176
176
|
def test_get_nic_attr_fw_bad(self, mock_get_fw, mock_get, mock_post, mock_delete):
|
|
177
177
|
|
|
178
178
|
async def fake_get_fw():
|
|
179
179
|
# Emit via Badfish logger name to match formatting
|
|
180
180
|
from logging import getLogger
|
|
181
181
|
|
|
182
|
-
getLogger("
|
|
182
|
+
getLogger("badfish.helpers.logger").error("Operation not supported by vendor.")
|
|
183
183
|
return 0
|
|
184
184
|
|
|
185
185
|
mock_get_fw.side_effect = fake_get_fw
|
|
@@ -89,7 +89,7 @@ class TestPowerOff(TestBase):
|
|
|
89
89
|
@patch("aiohttp.ClientSession.delete")
|
|
90
90
|
@patch("aiohttp.ClientSession.post")
|
|
91
91
|
@patch("aiohttp.ClientSession.get")
|
|
92
|
-
@patch("
|
|
92
|
+
@patch("badfish.main.Badfish.get_request")
|
|
93
93
|
def test_power_off_none(self, mock_get_req_call, mock_get, mock_post, mock_delete):
|
|
94
94
|
# The power off operation should return None when getting power state
|
|
95
95
|
mock_get_req_call.side_effect = [None]
|
|
@@ -139,7 +139,7 @@ class TestPowerState(TestBase):
|
|
|
139
139
|
@patch("aiohttp.ClientSession.delete")
|
|
140
140
|
@patch("aiohttp.ClientSession.post")
|
|
141
141
|
@patch("aiohttp.ClientSession.get")
|
|
142
|
-
@patch("
|
|
142
|
+
@patch("badfish.main.Badfish.get_request")
|
|
143
143
|
def test_power_state_none(self, mock_get_req_call, mock_get, mock_post, mock_delete):
|
|
144
144
|
# The power state check should return None (simulating communication failure)
|
|
145
145
|
mock_get_req_call.side_effect = [None]
|
|
@@ -31,7 +31,7 @@ class TestPowerConsumed(TestBase):
|
|
|
31
31
|
@patch("aiohttp.ClientSession.get")
|
|
32
32
|
def test_power_consumed_404(self, mock_get, mock_post, mock_delete):
|
|
33
33
|
# Mock the get_request method to return a 404 response that properly triggers vendor error
|
|
34
|
-
with patch("
|
|
34
|
+
with patch("badfish.main.Badfish.get_request") as mock_get_request:
|
|
35
35
|
from tests.test_base import MockResponse
|
|
36
36
|
|
|
37
37
|
# Mock get_request to return 404 for power endpoint
|
|
@@ -90,7 +90,7 @@ class TestRebootOnly(TestBase):
|
|
|
90
90
|
_, err = self.badfish_call()
|
|
91
91
|
assert err == RESPONSE_REBOOT_ONLY_SUCCESS_WITH_NG_RT
|
|
92
92
|
|
|
93
|
-
@patch("
|
|
93
|
+
@patch("badfish.main.RETRIES", 0)
|
|
94
94
|
@patch("aiohttp.ClientSession.delete")
|
|
95
95
|
@patch("aiohttp.ClientSession.get")
|
|
96
96
|
@patch("aiohttp.ClientSession.post")
|
|
@@ -164,7 +164,7 @@ class TestExportSCP(TestBase):
|
|
|
164
164
|
_, err = self.badfish_call()
|
|
165
165
|
assert err == RESPONSE_EXPORT_SCP_NO_LOCATION
|
|
166
166
|
|
|
167
|
-
@patch("
|
|
167
|
+
@patch("badfish.main.get_now", fixed_datetime)
|
|
168
168
|
@patch("aiohttp.ClientSession.delete")
|
|
169
169
|
@patch("aiohttp.ClientSession.post")
|
|
170
170
|
@patch("aiohttp.ClientSession.get")
|
|
@@ -254,7 +254,7 @@ class TestImportSCP(TestBase):
|
|
|
254
254
|
_, err = self.badfish_call()
|
|
255
255
|
assert err == RESPONSE_EXPORT_SCP_NO_LOCATION
|
|
256
256
|
|
|
257
|
-
@patch("
|
|
257
|
+
@patch("badfish.main.get_now", fixed_datetime)
|
|
258
258
|
@patch("aiohttp.ClientSession.delete")
|
|
259
259
|
@patch("aiohttp.ClientSession.post")
|
|
260
260
|
@patch("aiohttp.ClientSession.get")
|
badfish-1.0.6/pyproject.toml
DELETED
badfish-1.0.6/setup.py
DELETED
|
File without changes
|
|
@@ -1,215 +0,0 @@
|
|
|
1
|
-
# The MIT License
|
|
2
|
-
#
|
|
3
|
-
# Copyright (c) 2018 aio-libs team https://github.com/aio-libs/
|
|
4
|
-
# Copyright (c) 2017 Ocean S. A. https://ocean.io/
|
|
5
|
-
# Copyright (c) 2016-2017 WikiBusiness Corporation http://wikibusiness.org/
|
|
6
|
-
#
|
|
7
|
-
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
8
|
-
# of this software and associated documentation files (the "Software"), to deal
|
|
9
|
-
# in the Software without restriction, including without limitation the rights
|
|
10
|
-
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
11
|
-
# copies of the Software, and to permit persons to whom the Software is
|
|
12
|
-
# furnished to do so, subject to the following conditions:
|
|
13
|
-
#
|
|
14
|
-
# The above copyright notice and this permission notice shall be included in
|
|
15
|
-
# all copies or substantial portions of the Software.
|
|
16
|
-
#
|
|
17
|
-
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
18
|
-
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
19
|
-
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
20
|
-
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
21
|
-
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
22
|
-
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
23
|
-
# THE SOFTWARE.
|
|
24
|
-
|
|
25
|
-
import asyncio
|
|
26
|
-
from collections import OrderedDict
|
|
27
|
-
from functools import _CacheInfo, _make_key, partial, wraps
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
__version__ = "1.0.2"
|
|
31
|
-
|
|
32
|
-
__all__ = ("alru_cache",)
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
def unpartial(fn):
|
|
36
|
-
while hasattr(fn, "func"):
|
|
37
|
-
fn = fn.func
|
|
38
|
-
|
|
39
|
-
return fn
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
def _done_callback(fut, task):
|
|
43
|
-
if task.cancelled():
|
|
44
|
-
fut.cancel()
|
|
45
|
-
return
|
|
46
|
-
|
|
47
|
-
exc = task.exception()
|
|
48
|
-
if exc is not None:
|
|
49
|
-
fut.set_exception(exc)
|
|
50
|
-
return
|
|
51
|
-
|
|
52
|
-
fut.set_result(task.result())
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
def _cache_invalidate(wrapped, typed, *args, **kwargs):
|
|
56
|
-
key = _make_key(args, kwargs, typed)
|
|
57
|
-
|
|
58
|
-
exists = key in wrapped._cache
|
|
59
|
-
|
|
60
|
-
if exists:
|
|
61
|
-
wrapped._cache.pop(key)
|
|
62
|
-
|
|
63
|
-
return exists
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
def _cache_clear(wrapped):
|
|
67
|
-
wrapped.hits = wrapped.misses = 0
|
|
68
|
-
wrapped._cache = OrderedDict()
|
|
69
|
-
wrapped.tasks = set()
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
def _open(wrapped):
|
|
73
|
-
if not wrapped.closed:
|
|
74
|
-
raise RuntimeError("alru_cache is not closed")
|
|
75
|
-
|
|
76
|
-
was_closed = wrapped.hits == wrapped.misses == len(wrapped.tasks) == len(wrapped._cache) == 0
|
|
77
|
-
|
|
78
|
-
if not was_closed:
|
|
79
|
-
raise RuntimeError("alru_cache was not closed correctly")
|
|
80
|
-
|
|
81
|
-
wrapped.closed = False
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
def _close(wrapped, *, cancel=False, return_exceptions=True):
|
|
85
|
-
if wrapped.closed:
|
|
86
|
-
raise RuntimeError("alru_cache is closed")
|
|
87
|
-
|
|
88
|
-
wrapped.closed = True
|
|
89
|
-
|
|
90
|
-
if cancel:
|
|
91
|
-
for task in wrapped.tasks:
|
|
92
|
-
if not task.done(): # not sure is it possible
|
|
93
|
-
task.cancel()
|
|
94
|
-
|
|
95
|
-
return _wait_closed(wrapped, return_exceptions=return_exceptions)
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
async def _wait_closed(wrapped, *, return_exceptions):
|
|
99
|
-
wait_closed = asyncio.gather(*wrapped.tasks, return_exceptions=return_exceptions)
|
|
100
|
-
|
|
101
|
-
wait_closed.add_done_callback(partial(_close_waited, wrapped))
|
|
102
|
-
|
|
103
|
-
ret = await wait_closed
|
|
104
|
-
|
|
105
|
-
# hack to get _close_waited callback to be executed
|
|
106
|
-
await asyncio.sleep(0)
|
|
107
|
-
|
|
108
|
-
return ret
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
def _close_waited(wrapped, _):
|
|
112
|
-
wrapped.cache_clear()
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
def _cache_info(wrapped, maxsize):
|
|
116
|
-
return _CacheInfo(
|
|
117
|
-
wrapped.hits,
|
|
118
|
-
wrapped.misses,
|
|
119
|
-
maxsize,
|
|
120
|
-
len(wrapped._cache),
|
|
121
|
-
)
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
def __cache_touch(wrapped, key):
|
|
125
|
-
try:
|
|
126
|
-
wrapped._cache.move_to_end(key)
|
|
127
|
-
except KeyError: # not sure is it possible
|
|
128
|
-
pass
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
def _cache_hit(wrapped, key):
|
|
132
|
-
wrapped.hits += 1
|
|
133
|
-
__cache_touch(wrapped, key)
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
def _cache_miss(wrapped, key):
|
|
137
|
-
wrapped.misses += 1
|
|
138
|
-
__cache_touch(wrapped, key)
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
def alru_cache(
|
|
142
|
-
fn=None,
|
|
143
|
-
maxsize=128,
|
|
144
|
-
typed=False,
|
|
145
|
-
*,
|
|
146
|
-
cache_exceptions=True,
|
|
147
|
-
):
|
|
148
|
-
def wrapper(fn):
|
|
149
|
-
_origin = unpartial(fn)
|
|
150
|
-
|
|
151
|
-
if not asyncio.iscoroutinefunction(_origin):
|
|
152
|
-
raise RuntimeError("Coroutine function is required, got {}".format(fn))
|
|
153
|
-
|
|
154
|
-
# functools.partialmethod support
|
|
155
|
-
if hasattr(fn, "_make_unbound_method"):
|
|
156
|
-
fn = fn._make_unbound_method()
|
|
157
|
-
|
|
158
|
-
@wraps(fn)
|
|
159
|
-
async def wrapped(*fn_args, **fn_kwargs):
|
|
160
|
-
if wrapped.closed:
|
|
161
|
-
raise RuntimeError("alru_cache is closed for {}".format(wrapped))
|
|
162
|
-
|
|
163
|
-
loop = asyncio.get_event_loop()
|
|
164
|
-
|
|
165
|
-
key = _make_key(fn_args, fn_kwargs, typed)
|
|
166
|
-
|
|
167
|
-
fut = wrapped._cache.get(key)
|
|
168
|
-
|
|
169
|
-
if fut is not None:
|
|
170
|
-
if not fut.done():
|
|
171
|
-
_cache_hit(wrapped, key)
|
|
172
|
-
return await asyncio.shield(fut)
|
|
173
|
-
|
|
174
|
-
exc = fut._exception
|
|
175
|
-
|
|
176
|
-
if exc is None or cache_exceptions:
|
|
177
|
-
_cache_hit(wrapped, key)
|
|
178
|
-
return fut.result()
|
|
179
|
-
|
|
180
|
-
# exception here and cache_exceptions == False
|
|
181
|
-
wrapped._cache.pop(key)
|
|
182
|
-
|
|
183
|
-
fut = loop.create_future()
|
|
184
|
-
task = loop.create_task(fn(*fn_args, **fn_kwargs))
|
|
185
|
-
task.add_done_callback(partial(_done_callback, fut))
|
|
186
|
-
|
|
187
|
-
wrapped.tasks.add(task)
|
|
188
|
-
task.add_done_callback(wrapped.tasks.remove)
|
|
189
|
-
|
|
190
|
-
wrapped._cache[key] = fut
|
|
191
|
-
|
|
192
|
-
if maxsize is not None and len(wrapped._cache) > maxsize:
|
|
193
|
-
wrapped._cache.popitem(last=False)
|
|
194
|
-
|
|
195
|
-
_cache_miss(wrapped, key)
|
|
196
|
-
return await asyncio.shield(fut)
|
|
197
|
-
|
|
198
|
-
_cache_clear(wrapped)
|
|
199
|
-
wrapped._origin = _origin
|
|
200
|
-
wrapped.closed = False
|
|
201
|
-
wrapped.cache_info = partial(_cache_info, wrapped, maxsize)
|
|
202
|
-
wrapped.cache_clear = partial(_cache_clear, wrapped)
|
|
203
|
-
wrapped.invalidate = partial(_cache_invalidate, wrapped, typed)
|
|
204
|
-
wrapped.close = partial(_close, wrapped)
|
|
205
|
-
wrapped.open = partial(_open, wrapped)
|
|
206
|
-
|
|
207
|
-
return wrapped
|
|
208
|
-
|
|
209
|
-
if fn is None:
|
|
210
|
-
return wrapper
|
|
211
|
-
|
|
212
|
-
if callable(fn) or hasattr(fn, "_make_unbound_method"):
|
|
213
|
-
return wrapper(fn)
|
|
214
|
-
|
|
215
|
-
raise NotImplementedError("{} decorating is not supported".format(fn))
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|