diff --git a/tests/req_unit_test.py b/tests/req_unit_test.py deleted file mode 100644 index c48c42f..0000000 --- a/tests/req_unit_test.py +++ /dev/null @@ -1,34 +0,0 @@ -"""Test availability of required packages.""" - -import unittest -from pathlib import Path - -import pkg_resources -import pytest - -_REQUIREMENTS_PATH = Path(__file__).parent.with_name("requirements.txt") -_EXTRA_REQUIREMENTS_PATH = Path(__file__).parent.parent / "requirements" - - -class TestRequirements(unittest.TestCase): - """Test availability of required packages.""" - - def test_requirements(self): - """Test that each required package is available.""" - requirements = pkg_resources.parse_requirements(_REQUIREMENTS_PATH.open()) - for requirement in requirements: - requirement = str(requirement) - with self.subTest(requirement=requirement): - pkg_resources.require(requirement) - - @pytest.mark.xfail(strict=False) # DO not successfully parse recursing of reqs using -r - def test_extra_requirements(self): - """Test that each required package is available.""" - if _EXTRA_REQUIREMENTS_PATH.exists(): - for extra_req_file in _EXTRA_REQUIREMENTS_PATH.iterdir(): - if extra_req_file.is_file() and extra_req_file.suffix == ".txt": - requirements = pkg_resources.parse_requirements(extra_req_file.open()) - for requirement in requirements: - requirement = str(requirement) - with self.subTest(requirement=requirement): - pkg_resources.require(requirement) diff --git a/warg/__init__.py b/warg/__init__.py index 18f132e..2248714 100644 --- a/warg/__init__.py +++ b/warg/__init__.py @@ -1,14 +1,13 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- - +from importlib import resources +from importlib.metadata import Distribution, PackageNotFoundError from warnings import warn -import pkg_resources - __project__ = "Warg" __author__ = "Christian Heider Nielsen" -__version__ = "1.1.7" +__version__ = "1.1.8" __doc__ = r""" Created on 27/04/2019 @@ -18,6 +17,7 @@ from pathlib import Path + with open(Path(__file__).parent / "README.md", "r") as this_init_file: __doc__ += this_init_file.read() # del Path @@ -44,7 +44,6 @@ from .boolean_tests import * from .map_itertools import * from .ast_ops import * - from .importing import * from .functions import * from .os_utilities import * from .generators import * @@ -82,16 +81,20 @@ # from apppath import AppPath # CAREFUL CIRCULAR DEPENDENCY WARNING! # PROJECT_APP_PATH = AppPath(app_name=PROJECT_NAME, app_author=PROJECT_AUTHOR) # NOT USED! -distributions = {v.key: v for v in pkg_resources.working_set} -if PROJECT_NAME in distributions: - distribution = distributions[PROJECT_NAME] - DEVELOP = dist_is_editable(distribution) -else: +PACKAGE_DATA_PATH = resources.files(PROJECT_NAME) / "data" + +try: + DEVELOP = package_is_editable(PROJECT_NAME) +except PackageNotFoundError as e: DEVELOP = True def get_version(append_time: Any = DEVELOP) -> str: - """description""" + """ + + :param append_time: + :return: + """ import datetime import os @@ -106,17 +109,17 @@ def get_version(append_time: Any = DEVELOP) -> str: if version: # Most git tags are prefixed with 'v' (example: v1.2.3) this is - # never desirable for artifact repositories, so we strip the + # never desirable for artefact repositories, so we strip the # leading 'v' if it's present. version = version[1:] if isinstance(version, str) and version.startswith("v") else version else: - # Default version is an ISO8601 compliant datetime. PyPI doesn't allow + # The Default version is an ISO8601 compliant datetime. PyPI doesn't allow # the colon ':' character in its versions, and time is required to allow # for multiple publications to master in one day. This datetime string # uses the 'basic' ISO8601 format for both its date and time components # to avoid issues with the colon character (ISO requires that date and # time components of a date-time string must be uniformly basic or - # extended, which is why the date component does not have dashes. + # extended, which is why the date component does not have dashes.) # # Publications using datetime versions should only be made from master # to represent the HEAD moving forward. @@ -133,3 +136,7 @@ def get_version(append_time: Any = DEVELOP) -> str: __version__ = get_version(append_time=True) __version_info__ = tuple(int(segment) for segment in __version__.split(".")) + + +if __name__ == "__main__": + print(__version__) diff --git a/warg/decorators/caching/look_up_table.py b/warg/decorators/caching/look_up_table.py index c6b7438..c6fbb2c 100644 --- a/warg/decorators/caching/look_up_table.py +++ b/warg/decorators/caching/look_up_table.py @@ -9,7 +9,16 @@ """ from time import sleep, time -from typing import Iterable, Mapping, Set, Tuple, Sequence, MutableMapping, Any, Callable +from typing import ( + Iterable, + Mapping, + Set, + Tuple, + Sequence, + MutableMapping, + Any, + Callable, +) from warg.decorators.hashing import make_hash diff --git a/warg/os_utilities/path_functions.py b/warg/os_utilities/path_functions.py index c76efc3..74d8a81 100644 --- a/warg/os_utilities/path_functions.py +++ b/warg/os_utilities/path_functions.py @@ -7,7 +7,13 @@ Created on 08/03/2020 """ -__all__ = ["ensure_existence", "path_rmtree", "sanitise_path", "path_join", "keep_last_n_modified"] +__all__ = [ + "ensure_existence", + "path_rmtree", + "sanitise_path", + "path_join", + "keep_last_n_modified", +] import collections import os @@ -161,7 +167,11 @@ def ensure_existence( # @passes_kws_to(rmtree) def keep_last_n_modified( - directory: Union[Path, str], n: int, only_directories: bool = False, only_files: bool = False, **kwargs + directory: Union[Path, str], + n: int, + only_directories: bool = False, + only_files: bool = False, + **kwargs, ): directory = Path(directory) from shutil import rmtree diff --git a/warg/packages.py b/warg/packages.py deleted file mode 100644 index e12ff49..0000000 --- a/warg/packages.py +++ /dev/null @@ -1,25 +0,0 @@ -from typing import Any - -__all__ = ["dist_is_editable", "package_is_editable"] - -import pkg_resources - - -def dist_is_editable(dist: Any) -> bool: - """ - Return True if given Distribution is an editable installation.""" - import sys - from pathlib import Path - - for path_item in sys.path: - egg_link = Path(path_item) / f"{dist.project_name}.egg-link" - if egg_link.is_file(): - return True - return False - - -def package_is_editable(package_name: Any) -> bool: - distributions = {v.key: v for v in pkg_resources.working_set} - if package_name in distributions: - distribution = distributions[package_name] - return dist_is_editable(distribution) diff --git a/warg/packages/README.md b/warg/packages/README.md new file mode 100644 index 0000000..9dcad6f --- /dev/null +++ b/warg/packages/README.md @@ -0,0 +1 @@ +# Packages \ No newline at end of file diff --git a/warg/packages/__init__.py b/warg/packages/__init__.py new file mode 100644 index 0000000..ad8ab78 --- /dev/null +++ b/warg/packages/__init__.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +__doc__ = r""" + + Created on 31/07/2023 + """ + +__author__ = "Christian Heider Nielsen" + +from pathlib import Path + +with open(Path(__file__).parent / "README.md", "r") as this_init_file: + __doc__ += this_init_file.read() + +from .pip_parsing import * +from .reloading import * +from .editable import * diff --git a/warg/packages/editable.py b/warg/packages/editable.py new file mode 100644 index 0000000..aa4d23a --- /dev/null +++ b/warg/packages/editable.py @@ -0,0 +1,93 @@ +import json +from importlib.metadata import Distribution + +__all__ = [ + "dist_is_editable", + "package_is_editable", + "get_dist_package_location", + "get_package_location", +] + +from pathlib import Path + + +def dist_is_editable(dist: Distribution) -> bool: + """ + Return True if given Distribution is an editable installation. + """ + + top_level_name = dist.read_text("top_level.txt").split("\n")[0].strip() + + if dist._read_files_egginfo() is not None: + if top_level_name == dist._path.parent.stem: + return True + + if dist._read_files_distinfo() is not None: + direct_url_str = dist.read_text("direct_url.json") + if direct_url_str is not None: + direct_url_json = json.loads(direct_url_str) + if "dir_info" in direct_url_json: + if "editable" in direct_url_json["dir_info"]: + return direct_url_json["dir_info"]["editable"] + + return False + + +def package_is_editable(package_name: str) -> bool: + """ + Return True if given Package is an editable installation. + """ + return dist_is_editable(Distribution.from_name(package_name)) + + +def get_package_location(package_name: str) -> Path: + dist = Distribution.from_name(package_name) + if dist: + return get_dist_package_location(dist) + + +def get_dist_package_location(dist: Distribution) -> Path: + """ + FULL OF ASSUMPTIONS! + + :param dist: + :return: + """ + + top_level_name = dist.read_text("top_level.txt").split("\n")[0].strip() + + if dist._read_files_egginfo() is not None: + if top_level_name == dist._path.parent.stem: + return dist._path.parent + + if dist._read_files_distinfo() is not None: + direct_url_str = dist.read_text("direct_url.json") + if direct_url_str is not None: + direct_url_json = json.loads(direct_url_str) + if "dir_info" in direct_url_json: + if "editable" in direct_url_json["dir_info"]: + return Path(direct_url_json["url"]) + + if top_level_name: + package_location = dist._path.parent / top_level_name + if package_location.exists() and package_location.is_dir(): + return package_location + + return None + + +if __name__ == "__main__": + print(package_is_editable(package_name="draugr")) + print(get_package_location(package_name="draugr")) + + print(package_is_editable(package_name="warg")) + print(get_package_location(package_name="warg")) + + print(package_is_editable(package_name="apppath")) + print(get_package_location(package_name="apppath")) + + print(get_package_location(package_name="numpy")) + + print(package_is_editable(package_name="Pillow")) + print(get_package_location(package_name="Pillow")) + print(get_package_location(package_name="pillow")) diff --git a/warg/packages/pip_parsing.py b/warg/packages/pip_parsing.py new file mode 100644 index 0000000..45248bc --- /dev/null +++ b/warg/packages/pip_parsing.py @@ -0,0 +1,38 @@ +from pathlib import Path +from typing import List, Union + +from packaging.requirements import Requirement +from pip._internal.network.session import PipSession +from pip._internal.req import parse_requirements +from pip._internal.req.req_file import ParsedRequirement +from pip._internal.utils.packaging import get_requirement + + +from urllib.parse import urlparse + +__all__ = ["get_requirements_from_file"] + + +def get_reqed(req: ParsedRequirement) -> Requirement: + req_ = req.requirement + if req.is_editable: # parse out egg=... fragment from VCS URL + parsed = urlparse(req_) + egg_name = parsed.fragment.partition("egg=")[-1] + without_fragment = parsed._replace(fragment="").geturl() + req_parsed = f"{egg_name} @ {without_fragment}" + else: + req_parsed = req_ + return get_requirement(req_parsed) + + +def get_requirements_from_file( + file_path: Union[str, Path], session: Union[str, PipSession] = "test" +) -> List[Requirement]: + """Turn requirements.txt into a list""" + if isinstance(file_path, Path): + file_path = str(file_path) + return [get_reqed(ir) for ir in parse_requirements(file_path, session=session)] + + +if __name__ == "__main__": + print(get_requirements_from_file(Path(__file__).parent.parent.parent / "requirements.txt")) diff --git a/warg/importing.py b/warg/packages/reloading.py similarity index 96% rename from warg/importing.py rename to warg/packages/reloading.py index 451f9bd..9248737 100644 --- a/warg/importing.py +++ b/warg/packages/reloading.py @@ -30,9 +30,9 @@ from typing import Optional, Any, Union, List, Iterable, Callable from warnings import warn -import pkg_resources -from warg import passes_kws_to +from warg.packages import get_requirements_from_file +from warg.decorators import passes_kws_to """ PRELOADED_MODULES = set() @@ -94,9 +94,8 @@ def reload_requirements(requirements_path: Path, containment_test: Callable = co :param containment_test: :return: """ - with open(requirements_path) as f: - for r in pkg_resources.parse_requirements(f.readlines()): - reload_module(r.project_name, containment_test=containment_test) + for r in get_requirements_from_file(requirements_path): + reload_module(r.name, containment_test=containment_test) def reload_all_modules(catch_exceptions: bool = True, verbose: bool = True) -> None: @@ -187,7 +186,7 @@ def walk_down(path: Path, max_descent: int = None): def find_ancestral_relatives( target: Union[str, Path], - context: Path, # = Path.cwd(), + context: Path = Path.cwd(), *, from_parent_of_context: bool = True, ancestral_levels: int = 2, @@ -416,7 +415,7 @@ def iajsd(): print(s == s2, set(s2) - set(s), set(s) - set(s2), s2) def asuhdsaud(): - print(find_ancestral_relatives("queues")) + print(find_ancestral_relatives("queues", context=__file__)) # _main() # aisjdi() diff --git a/warg/plugin.py b/warg/plugin.py index 3452b74..c73c369 100644 --- a/warg/plugin.py +++ b/warg/plugin.py @@ -7,9 +7,8 @@ Created on 13/06/2020 """ -from typing import Tuple - -import pkg_resources +from importlib.metadata import entry_points, EntryPoint +from typing import Tuple, Any, Generator, Union __all__ = ["get_plugins", "get_static_plugins", "get_dynamic_plugins"] @@ -41,8 +40,10 @@ def get_static_plugins(package_name: str) -> Tuple: return () -def get_dynamic_plugins(package_name: str) -> Tuple: - """Returns a list specifying dynamically loaded plugins. +def get_dynamic_plugins( + package_name: str, +) -> Generator[Union[str, EntryPoint], Any, None]: + """Returns a list specifying dynamically loaded plugins. Returns: The list of dynamic plugins. @@ -53,7 +54,7 @@ def get_dynamic_plugins(package_name: str) -> Tuple: # .load() method to import and load that entry point (module or object). # from importlib import metadata # new method! # return [ entry_point.load() for entry_point in metadata.entry_points()[f'{package_name}_plugins'] ] - return (entry_point.load() for entry_point in pkg_resources.iter_entry_points(f"{package_name}_plugins")) + return (entry_point for entry_point in entry_points(group="console_scripts", name=package_name)) if __name__ == "__main__":