├── .bazelignore ├── .gitignore ├── BUILD.bazel ├── LICENSE ├── README.md ├── WORKSPACE ├── build_env.py ├── example ├── BUILD.bazel ├── WORKSPACE ├── libraries │ ├── liba │ │ ├── BUILD.bazel │ │ └── liba.py │ └── libb │ │ ├── BUILD.bazel │ │ └── libb.py ├── requirements.in └── requirements.txt ├── requirements.txt ├── vendor ├── importlib_metadata │ ├── BUILD.bazel │ ├── CHANGES.rst │ ├── LICENSE │ ├── README.rst │ └── importlib_metadata │ │ ├── __init__.py │ │ ├── _adapters.py │ │ ├── _collections.py │ │ ├── _compat.py │ │ ├── _functools.py │ │ ├── _itertools.py │ │ ├── _meta.py │ │ ├── _py39compat.py │ │ ├── _text.py │ │ └── py.typed └── zipp │ ├── BUILD.bazel │ ├── CHANGES.rst │ ├── LICENSE │ ├── README.rst │ └── zipp.py └── venv.bzl /.bazelignore: -------------------------------------------------------------------------------- 1 | example -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bazel-* 2 | env 3 | -------------------------------------------------------------------------------- /BUILD.bazel: -------------------------------------------------------------------------------- 1 | # Copyright 2021 cedar.ai. All rights reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | package(default_visibility = ["//visibility:public"]) 16 | 17 | exports_files([ 18 | "build_env.py", 19 | "venv.bzl", 20 | ]) 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Bazel rules for creating Python virtual environments. 2 | See `example/` for an example. 3 | 4 | ## Installation 5 | Add the following to your `WORKSPACE`. 6 | 7 | (Note: see the releases page for release-specific `WORKSPACE` config) 8 | 9 | ``` 10 | load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") 11 | 12 | http_archive( 13 | name = "rules_pyvenv", 14 | strip_prefix = "rules_pyvenv-main", 15 | url = "https://github.com/cedarai/rules_pyvenv/archive/main.tar.gz", 16 | ) 17 | ``` 18 | 19 | These rules require a recent version of Python 3.6+ and `rules_python`. 20 | The environment is built using the `venv` library that ships with the Python standard library. 21 | If using the system-provided Python on Debian/Ubuntu, you may need to run 22 | ``` 23 | apt install python3.8-venv 24 | ``` 25 | 26 | On Windows, [you need to enable symlink support](https://bazel.build/configure/windows#symlink). 27 | 28 | ## Example 29 | ``` 30 | $ cd example 31 | $ bazel run //:venv env 32 | $ . env/bin/activate 33 | ``` 34 | -------------------------------------------------------------------------------- /WORKSPACE: -------------------------------------------------------------------------------- 1 | # Copyright 2021 cedar.ai. All rights reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | workspace(name = "rules_pyvenv") 16 | -------------------------------------------------------------------------------- /build_env.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 cedar.ai. All rights reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from enum import Enum 16 | import itertools 17 | import json 18 | import os 19 | import pathlib 20 | import subprocess 21 | import sys 22 | import textwrap 23 | import venv 24 | from typing import Dict, List, NamedTuple 25 | 26 | import importlib_metadata 27 | 28 | 29 | class EnvPathType(Enum): 30 | SITE_PACKAGES = 1 31 | DATA = 2 32 | 33 | 34 | class EnvFile(NamedTuple): 35 | path: pathlib.Path 36 | env_path: pathlib.Path 37 | type_: EnvPathType = EnvPathType.SITE_PACKAGES 38 | 39 | 40 | def console_script(env_path: pathlib.Path, module: str, func: str) -> str: 41 | return textwrap.dedent( 42 | f"""\ 43 | #!{env_path / "bin/python3"} 44 | # -*- coding: utf-8 -*- 45 | import re 46 | import sys 47 | from {module} import {func} 48 | if __name__ == '__main__': 49 | sys.argv[0] = re.sub(r'(-script\\.pyw|\\.exe)?$', '', sys.argv[0]) 50 | sys.exit({func}()) 51 | """ 52 | ) 53 | 54 | 55 | def path_starts_with(path: pathlib.Path, prefix: pathlib.Path) -> bool: 56 | return path.parts[: len(prefix.parts)] == prefix.parts 57 | 58 | 59 | def get_env_path( 60 | workspace: str, path: pathlib.Path, imports: List[pathlib.Path] 61 | ) -> EnvFile: 62 | # Import prefixes start with the workspace name, which might be the local workspace. 63 | # We first normalize the given path so that it starts with its workspace name. 64 | if path.parts[0] == "..": 65 | wspath = path.relative_to("..") 66 | is_external = True 67 | else: 68 | wspath = pathlib.Path(workspace) / path 69 | is_external = False 70 | 71 | for imp in imports: 72 | if path_starts_with(wspath, imp): 73 | return EnvFile(path, wspath.relative_to(imp)) 74 | 75 | imp_data = imp.parent / "data" 76 | if path_starts_with(wspath, imp_data): 77 | return EnvFile(path, wspath.relative_to(imp_data), EnvPathType.DATA) 78 | 79 | if not is_external: 80 | # If the input wasn't an external path and it didn't match any import prefixes, 81 | # just return it as given. 82 | return EnvFile(path, path) 83 | 84 | # External file that didn't match imports. Include but warn. 85 | # We include it as relative to its workspace directory, so strip the first component 86 | # off wspath. 87 | include_path = wspath.relative_to(wspath.parts[0]) 88 | print(f"Warning: [{path}] didn't match any imports. Including as [{include_path}]") 89 | 90 | return EnvFile(path, include_path) 91 | 92 | 93 | def is_external(file_: pathlib.Path) -> bool: 94 | return file_.parts[0] == ".." 95 | 96 | 97 | def find_site_packages(env_path: pathlib.Path) -> pathlib.Path: 98 | if sys.platform == 'win32': 99 | lib_path = env_path / "Lib" 100 | 101 | site_packages_path = lib_path / "site-packages" 102 | if site_packages_path.exists(): 103 | return site_packages_path 104 | 105 | else: 106 | lib_path = env_path / "lib" 107 | 108 | # We should find one "pythonX.X" directory in here. 109 | for child in lib_path.iterdir(): 110 | if child.name.startswith("python"): 111 | site_packages_path = child / "site-packages" 112 | if site_packages_path.exists(): 113 | return site_packages_path 114 | 115 | raise Exception("Unable to find site-packages path in venv") 116 | 117 | 118 | def get_files(build_env_input: Dict) -> List[EnvFile]: 119 | files = [] 120 | 121 | always_link = build_env_input.get("always_link", False) 122 | imports = [pathlib.Path(imp) for imp in build_env_input["imports"]] 123 | workspace = build_env_input["workspace"] 124 | for depfile in build_env_input["files"]: 125 | # Bucket files into external and workspace groups. 126 | # Only generated workspace files are kept. 127 | type_ = depfile["t"] 128 | input_path = pathlib.Path(depfile["p"]) 129 | 130 | # If this is a directory, expand to each recursive child. 131 | if input_path.is_dir(): 132 | paths = input_path.glob("**/*") 133 | paths = [p for p in paths if not p.is_dir()] 134 | else: 135 | paths = [input_path] 136 | 137 | for path in paths: 138 | env_file = get_env_path(workspace, path, imports) 139 | if env_file.env_path != path or always_link: 140 | files.append(env_file) 141 | 142 | return files 143 | 144 | 145 | def is_data_file(file: EnvFile) -> bool: 146 | return ( 147 | file.type_ == EnvPathType.DATA 148 | or file.env_path.parts[0].endswith(".data") 149 | ) 150 | 151 | 152 | def install_data_file(env_path: pathlib.Path, file: EnvFile) -> None: 153 | if ( 154 | len(file.env_path.parts) > 2 155 | and file.env_path.parts[1] == "scripts" 156 | ): 157 | install_included_script(env_path, file.path) 158 | elif file.type_ == EnvPathType.DATA: 159 | install_site_file(env_path, file) 160 | 161 | 162 | def install_site_file(site_packages_path: pathlib.Path, file: EnvFile) -> None: 163 | site_path = site_packages_path / file.env_path 164 | if not site_path.exists(): 165 | site_path.parent.mkdir(parents=True, exist_ok=True) 166 | site_path.symlink_to(file.path.resolve()) 167 | 168 | 169 | def install_files(env_path: pathlib.Path, files: List[EnvFile]) -> None: 170 | site_packages_path = find_site_packages(env_path) 171 | for file in files: 172 | if is_data_file(file): 173 | install_data_file(env_path, file) 174 | else: 175 | install_site_file(site_packages_path, file) 176 | 177 | 178 | # A copy of importlib_metadata:entry_points that takes a list of search paths. 179 | def entry_points(path: List[str], **params) -> importlib_metadata.EntryPoints: 180 | eps = itertools.chain.from_iterable( 181 | dist.entry_points 182 | for dist in importlib_metadata._unique( 183 | importlib_metadata.distributions(path=path) 184 | ) 185 | ) 186 | return importlib_metadata.EntryPoints(eps).select(**params) 187 | 188 | 189 | def generate_console_scripts(env_path: pathlib.Path) -> None: 190 | site_packages = find_site_packages(env_path) 191 | if sys.platform == 'win32': 192 | bin_path = env_path / "Scripts" 193 | else: 194 | bin_path = env_path / "bin" 195 | 196 | console_scripts = entry_points(path=[str(site_packages)], group="console_scripts") 197 | for ep in console_scripts: 198 | script = bin_path / ep.name 199 | if script.exists(): 200 | continue 201 | script.write_text( 202 | console_script(env_path, ep.module, ep.attr), encoding="utf-8" 203 | ) 204 | script.chmod(0o755) 205 | 206 | 207 | def install_included_script(env_path: pathlib.Path, script_file: pathlib.Path) -> None: 208 | script_text = script_file.read_bytes() 209 | 210 | # From pep-491: 211 | # Python scripts must appear in scripts and begin with exactly b'#!python' in order to enjoy script wrapper 212 | # generation and #!python rewriting at install time. They may have any or no extension. 213 | if script_text.startswith(b"#!python"): 214 | shebang = f'#!{env_path / "bin/python3"}'.encode("utf-8") 215 | script_text = shebang + script_text[len(b"#!python") :] 216 | 217 | script = env_path / "bin" / script_file.name 218 | script.write_bytes(script_text) 219 | script.chmod(0o755) 220 | 221 | 222 | def run_additional_commands(env_path: pathlib.Path, commands: List[str]) -> None: 223 | lines = [f". {env_path}/bin/activate"] 224 | for cmd in commands: 225 | pip_cmd = f"pip --no-input {cmd}" 226 | # Echo in green what command is being run 227 | lines.append(rf'echo "\n\033[0;32m> {pip_cmd}\033[0m"') 228 | lines.append(pip_cmd) 229 | 230 | full_command = ";".join(lines) 231 | 232 | # Prefer using zsh, since on macos (which ships with it), zsh adds support for executing 233 | # scripts whose shebang lines point to other scripts. 234 | # If we can't find zsh, use the default and hope for the best. 235 | shell = None 236 | for zsh in ["/bin/zsh", "/usr/bin/zsh"]: 237 | if pathlib.Path(zsh).exists(): 238 | shell = zsh 239 | ret = subprocess.run( 240 | full_command, capture_output=False, shell=True, executable=shell 241 | ) 242 | ret.check_returncode() 243 | 244 | 245 | def main(): 246 | if "BUILD_ENV_INPUT" not in os.environ: 247 | raise Exception("Missing BUILD_ENV_INPUT environment variable") 248 | if "VENV_LOCATION" not in os.environ and len(sys.argv) != 2: 249 | raise Exception(f"Usage: {sys.argv} ") 250 | 251 | with open(os.environ["BUILD_ENV_INPUT"]) as f: 252 | build_env_input = json.load(f) 253 | 254 | files = get_files(build_env_input) 255 | 256 | # Hack: fully resolve the current interpreter's known path to get venv to link to the 257 | # files in their actual location 258 | sys._base_executable = str(pathlib.Path(sys._base_executable).resolve()) 259 | 260 | if "VENV_LOCATION" in os.environ: 261 | env_path = pathlib.Path(os.environ["BUILD_WORKSPACE_DIRECTORY"]) / os.environ["VENV_LOCATION"] 262 | else: 263 | cwd = os.environ.get("BUILD_WORKING_DIRECTORY", os.getcwd()) 264 | env_path = pathlib.Path(cwd) / pathlib.Path(sys.argv[1]) 265 | 266 | builder = venv.EnvBuilder(clear=True, symlinks=True, with_pip=True) 267 | builder.create(str(env_path)) 268 | 269 | install_files(env_path, files) 270 | generate_console_scripts(env_path) 271 | 272 | extra_commands = build_env_input.get("commands") 273 | if extra_commands: 274 | run_additional_commands(env_path, extra_commands) 275 | 276 | 277 | if __name__ == "__main__": 278 | main() 279 | -------------------------------------------------------------------------------- /example/BUILD.bazel: -------------------------------------------------------------------------------- 1 | # Copyright 2021 cedar.ai. All rights reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | package(default_visibility = ["//visibility:public"]) 16 | 17 | load("@rules_pyvenv//:venv.bzl", "py_venv") 18 | load("@example_deps//:requirements.bzl", "requirement", "data_requirement") 19 | load("@rules_python//python/pip_install:requirements.bzl", "compile_pip_requirements") 20 | 21 | py_venv( 22 | name = "venv", 23 | deps = [ 24 | "//libraries/liba", 25 | requirement("black"), 26 | requirement("numpy"), 27 | ], 28 | data = [ 29 | data_requirement("black"), 30 | ], 31 | extra_pip_commands = [ 32 | # "install ipython jupyter-console", 33 | ] 34 | ) 35 | 36 | py_venv( 37 | name = "venv_only_local", 38 | deps = [ 39 | "//libraries/liba", 40 | "//libraries/libb", 41 | ], 42 | ) 43 | 44 | py_venv( 45 | name = "venv_only_local_always_link", 46 | deps = [ 47 | "//libraries/liba", 48 | "//libraries/libb", 49 | ], 50 | always_link = True, 51 | ) 52 | 53 | py_venv( 54 | name = "venv_relative_workspace_root", 55 | venv_location = ".venv", 56 | deps = [ 57 | "//libraries/liba", 58 | "//libraries/libb", 59 | ], 60 | ) 61 | 62 | compile_pip_requirements( 63 | name = "requirements", 64 | extra_args = ["--allow-unsafe"], 65 | requirements_in = "requirements.in", 66 | requirements_txt = "requirements.txt", 67 | ) 68 | -------------------------------------------------------------------------------- /example/WORKSPACE: -------------------------------------------------------------------------------- 1 | # Copyright 2021 cedar.ai. All rights reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | workspace(name = "example") 16 | 17 | load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") 18 | 19 | # Required rules_python 20 | http_archive( 21 | name = "rules_python", 22 | sha256 = "0a8003b044294d7840ac7d9d73eef05d6ceb682d7516781a4ec62eeb34702578", 23 | strip_prefix = "rules_python-0.24.0", 24 | url = "https://github.com/bazelbuild/rules_python/releases/download/0.24.0/rules_python-0.24.0.tar.gz", 25 | ) 26 | 27 | load("@rules_python//python:repositories.bzl", "py_repositories", "python_register_toolchains") 28 | 29 | py_repositories() 30 | 31 | python_register_toolchains( 32 | name = "python3_10", 33 | # Available versions are listed in @rules_python//python:versions.bzl. 34 | # We recommend using the same version your team is already standardized on. 35 | python_version = "3.10", 36 | ) 37 | 38 | # For this example, we load rules_pyvenv locally. But you'd use http_archive. 39 | local_repository( 40 | name = "rules_pyvenv", 41 | path = "../" 42 | ) 43 | 44 | # Fetch some python packages to install in the venv, 45 | load("@rules_python//python:pip.bzl", "pip_parse") 46 | pip_parse( 47 | name = "example_deps", 48 | requirements_lock = "//:requirements.txt", 49 | ) 50 | 51 | load("@example_deps//:requirements.bzl", "install_deps") 52 | 53 | install_deps() 54 | -------------------------------------------------------------------------------- /example/libraries/liba/BUILD.bazel: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//visibility:public"]) 2 | 3 | py_library( 4 | name = "liba", 5 | srcs = ["liba.py"], 6 | imports = ["."], 7 | ) 8 | 9 | -------------------------------------------------------------------------------- /example/libraries/liba/liba.py: -------------------------------------------------------------------------------- 1 | def foo(): 2 | pass 3 | 4 | -------------------------------------------------------------------------------- /example/libraries/libb/BUILD.bazel: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//visibility:public"]) 2 | 3 | py_library( 4 | name = "libb", 5 | srcs = ["libb.py"], 6 | ) 7 | 8 | -------------------------------------------------------------------------------- /example/libraries/libb/libb.py: -------------------------------------------------------------------------------- 1 | def foo(): 2 | pass 3 | 4 | -------------------------------------------------------------------------------- /example/requirements.in: -------------------------------------------------------------------------------- 1 | black==22.10.0 2 | numpy==1.23.3 3 | -------------------------------------------------------------------------------- /example/requirements.txt: -------------------------------------------------------------------------------- 1 | # 2 | # This file is autogenerated by pip-compile with Python 3.10 3 | # by the following command: 4 | # 5 | # bazel run //:requirements.update 6 | # 7 | black==22.10.0 \ 8 | --hash=sha256:14ff67aec0a47c424bc99b71005202045dc09270da44a27848d534600ac64fc7 \ 9 | --hash=sha256:197df8509263b0b8614e1df1756b1dd41be6738eed2ba9e9769f3880c2b9d7b6 \ 10 | --hash=sha256:1e464456d24e23d11fced2bc8c47ef66d471f845c7b7a42f3bd77bf3d1789650 \ 11 | --hash=sha256:2039230db3c6c639bd84efe3292ec7b06e9214a2992cd9beb293d639c6402edb \ 12 | --hash=sha256:21199526696b8f09c3997e2b4db8d0b108d801a348414264d2eb8eb2532e540d \ 13 | --hash=sha256:2644b5d63633702bc2c5f3754b1b475378fbbfb481f62319388235d0cd104c2d \ 14 | --hash=sha256:432247333090c8c5366e69627ccb363bc58514ae3e63f7fc75c54b1ea80fa7de \ 15 | --hash=sha256:444ebfb4e441254e87bad00c661fe32df9969b2bf224373a448d8aca2132b395 \ 16 | --hash=sha256:5b9b29da4f564ba8787c119f37d174f2b69cdfdf9015b7d8c5c16121ddc054ae \ 17 | --hash=sha256:5cc42ca67989e9c3cf859e84c2bf014f6633db63d1cbdf8fdb666dcd9e77e3fa \ 18 | --hash=sha256:5d8f74030e67087b219b032aa33a919fae8806d49c867846bfacde57f43972ef \ 19 | --hash=sha256:72ef3925f30e12a184889aac03d77d031056860ccae8a1e519f6cbb742736383 \ 20 | --hash=sha256:819dc789f4498ecc91438a7de64427c73b45035e2e3680c92e18795a839ebb66 \ 21 | --hash=sha256:915ace4ff03fdfff953962fa672d44be269deb2eaf88499a0f8805221bc68c87 \ 22 | --hash=sha256:9311e99228ae10023300ecac05be5a296f60d2fd10fff31cf5c1fa4ca4b1988d \ 23 | --hash=sha256:974308c58d057a651d182208a484ce80a26dac0caef2895836a92dd6ebd725e0 \ 24 | --hash=sha256:b8b49776299fece66bffaafe357d929ca9451450f5466e997a7285ab0fe28e3b \ 25 | --hash=sha256:c957b2b4ea88587b46cf49d1dc17681c1e672864fd7af32fc1e9664d572b3458 \ 26 | --hash=sha256:e41a86c6c650bcecc6633ee3180d80a025db041a8e2398dcc059b3afa8382cd4 \ 27 | --hash=sha256:f513588da599943e0cde4e32cc9879e825d58720d6557062d1098c5ad80080e1 \ 28 | --hash=sha256:fba8a281e570adafb79f7755ac8721b6cf1bbf691186a287e990c7929c7692ff 29 | # via -r requirements.in 30 | click==8.1.3 \ 31 | --hash=sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e \ 32 | --hash=sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48 33 | # via black 34 | mypy-extensions==0.4.3 \ 35 | --hash=sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d \ 36 | --hash=sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8 37 | # via black 38 | numpy==1.23.3 \ 39 | --hash=sha256:004f0efcb2fe1c0bd6ae1fcfc69cc8b6bf2407e0f18be308612007a0762b4089 \ 40 | --hash=sha256:09f6b7bdffe57fc61d869a22f506049825d707b288039d30f26a0d0d8ea05164 \ 41 | --hash=sha256:0ea3f98a0ffce3f8f57675eb9119f3f4edb81888b6874bc1953f91e0b1d4f440 \ 42 | --hash=sha256:17c0e467ade9bda685d5ac7f5fa729d8d3e76b23195471adae2d6a6941bd2c18 \ 43 | --hash=sha256:1f27b5322ac4067e67c8f9378b41c746d8feac8bdd0e0ffede5324667b8a075c \ 44 | --hash=sha256:22d43376ee0acd547f3149b9ec12eec2f0ca4a6ab2f61753c5b29bb3e795ac4d \ 45 | --hash=sha256:2ad3ec9a748a8943e6eb4358201f7e1c12ede35f510b1a2221b70af4bb64295c \ 46 | --hash=sha256:301c00cf5e60e08e04d842fc47df641d4a181e651c7135c50dc2762ffe293dbd \ 47 | --hash=sha256:39a664e3d26ea854211867d20ebcc8023257c1800ae89773cbba9f9e97bae036 \ 48 | --hash=sha256:51bf49c0cd1d52be0a240aa66f3458afc4b95d8993d2d04f0d91fa60c10af6cd \ 49 | --hash=sha256:78a63d2df1d947bd9d1b11d35564c2f9e4b57898aae4626638056ec1a231c40c \ 50 | --hash=sha256:7cd1328e5bdf0dee621912f5833648e2daca72e3839ec1d6695e91089625f0b4 \ 51 | --hash=sha256:8355fc10fd33a5a70981a5b8a0de51d10af3688d7a9e4a34fcc8fa0d7467bb7f \ 52 | --hash=sha256:8c79d7cf86d049d0c5089231a5bcd31edb03555bd93d81a16870aa98c6cfb79d \ 53 | --hash=sha256:91b8d6768a75247026e951dce3b2aac79dc7e78622fc148329135ba189813584 \ 54 | --hash=sha256:94c15ca4e52671a59219146ff584488907b1f9b3fc232622b47e2cf832e94fb8 \ 55 | --hash=sha256:98dcbc02e39b1658dc4b4508442a560fe3ca5ca0d989f0df062534e5ca3a5c1a \ 56 | --hash=sha256:a64403f634e5ffdcd85e0b12c08f04b3080d3e840aef118721021f9b48fc1460 \ 57 | --hash=sha256:bc6e8da415f359b578b00bcfb1d08411c96e9a97f9e6c7adada554a0812a6cc6 \ 58 | --hash=sha256:bdc9febce3e68b697d931941b263c59e0c74e8f18861f4064c1f712562903411 \ 59 | --hash=sha256:c1ba66c48b19cc9c2975c0d354f24058888cdc674bebadceb3cdc9ec403fb5d1 \ 60 | --hash=sha256:c9f707b5bb73bf277d812ded9896f9512a43edff72712f31667d0a8c2f8e71ee \ 61 | --hash=sha256:d5422d6a1ea9b15577a9432e26608c73a78faf0b9039437b075cf322c92e98e7 \ 62 | --hash=sha256:e5d5420053bbb3dd64c30e58f9363d7a9c27444c3648e61460c1237f9ec3fa14 \ 63 | --hash=sha256:e868b0389c5ccfc092031a861d4e158ea164d8b7fdbb10e3b5689b4fc6498df6 \ 64 | --hash=sha256:efd9d3abe5774404becdb0748178b48a218f1d8c44e0375475732211ea47c67e \ 65 | --hash=sha256:f8c02ec3c4c4fcb718fdf89a6c6f709b14949408e8cf2a2be5bfa9c49548fd85 \ 66 | --hash=sha256:ffcf105ecdd9396e05a8e58e81faaaf34d3f9875f137c7372450baa5d77c9a54 67 | # via -r requirements.in 68 | pathspec==0.10.1 \ 69 | --hash=sha256:46846318467efc4556ccfd27816e004270a9eeeeb4d062ce5e6fc7a87c573f93 \ 70 | --hash=sha256:7ace6161b621d31e7902eb6b5ae148d12cfd23f4a249b9ffb6b9fee12084323d 71 | # via black 72 | platformdirs==2.5.2 \ 73 | --hash=sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788 \ 74 | --hash=sha256:58c8abb07dcb441e6ee4b11d8df0ac856038f944ab98b7be6b27b2a3c7feef19 75 | # via black 76 | tomli==2.0.1 \ 77 | --hash=sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc \ 78 | --hash=sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f 79 | # via black 80 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | entrypoints==0.3 2 | -------------------------------------------------------------------------------- /vendor/importlib_metadata/BUILD.bazel: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//visibility:public"]) 2 | 3 | py_library( 4 | name = "importlib_metadata", 5 | srcs = glob(["**/*.py"]), 6 | deps = ["//vendor/zipp"], 7 | imports = ["."], 8 | ) 9 | -------------------------------------------------------------------------------- /vendor/importlib_metadata/CHANGES.rst: -------------------------------------------------------------------------------- 1 | v5.0.0 2 | ====== 3 | 4 | * #97, #284, #300: Removed compatibility shims for deprecated entry 5 | point interfaces. 6 | 7 | v4.13.0 8 | ======= 9 | 10 | * #396: Added compatibility for ``PathDistributions`` originating 11 | from Python 3.8 and 3.9. 12 | 13 | v4.12.0 14 | ======= 15 | 16 | * py-93259: Now raise ``ValueError`` when ``None`` or an empty 17 | string are passed to ``Distribution.from_name`` (and other 18 | callers). 19 | 20 | v4.11.4 21 | ======= 22 | 23 | * #379: In ``PathDistribution._name_from_stem``, avoid including 24 | parts of the extension in the result. 25 | * #381: In ``PathDistribution._normalized_name``, ensure names 26 | loaded from the stem of the filename are also normalized, ensuring 27 | duplicate entry points by packages varying only by non-normalized 28 | name are hidden. 29 | 30 | v4.11.3 31 | ======= 32 | 33 | * #372: Removed cast of path items in FastPath, not needed. 34 | 35 | v4.11.2 36 | ======= 37 | 38 | * #369: Fixed bug where ``EntryPoint.extras`` was returning 39 | match objects and not the extras strings. 40 | 41 | v4.11.1 42 | ======= 43 | 44 | * #367: In ``Distribution.requires`` for egg-info, if ``requires.txt`` 45 | is empty, return an empty list. 46 | 47 | v4.11.0 48 | ======= 49 | 50 | * bpo-46246: Added ``__slots__`` to ``EntryPoints``. 51 | 52 | v4.10.2 53 | ======= 54 | 55 | * #365 and bpo-46546: Avoid leaking ``method_name`` in 56 | ``DeprecatedList``. 57 | 58 | v4.10.1 59 | ======= 60 | 61 | v2.1.3 62 | ======= 63 | 64 | * #361: Avoid potential REDoS in ``EntryPoint.pattern``. 65 | 66 | v4.10.0 67 | ======= 68 | 69 | * #354: Removed ``Distribution._local`` factory. This 70 | functionality was created as a demonstration of the 71 | possible implementation. Now, the 72 | `pep517 `_ package 73 | provides this functionality directly through 74 | `pep517.meta.load `_. 75 | 76 | v4.9.0 77 | ====== 78 | 79 | * Require Python 3.7 or later. 80 | 81 | v4.8.3 82 | ====== 83 | 84 | * #357: Fixed requirement generation from egg-info when a 85 | URL requirement is given. 86 | 87 | v4.8.2 88 | ====== 89 | 90 | v2.1.2 91 | ====== 92 | 93 | * #353: Fixed discovery of distributions when path is empty. 94 | 95 | v4.8.1 96 | ====== 97 | 98 | * #348: Restored support for ``EntryPoint`` access by item, 99 | deprecating support in the process. Users are advised 100 | to use direct member access instead of item-based access:: 101 | 102 | - ep[0] -> ep.name 103 | - ep[1] -> ep.value 104 | - ep[2] -> ep.group 105 | - ep[:] -> ep.name, ep.value, ep.group 106 | 107 | v4.8.0 108 | ====== 109 | 110 | * #337: Rewrote ``EntryPoint`` as a simple class, still 111 | immutable and still with the attributes, but without any 112 | expectation for ``namedtuple`` functionality such as 113 | ``_asdict``. 114 | 115 | v4.7.1 116 | ====== 117 | 118 | * #344: Fixed regression in ``packages_distributions`` when 119 | neither top-level.txt nor a files manifest is present. 120 | 121 | v4.7.0 122 | ====== 123 | 124 | * #330: In ``packages_distributions``, now infer top-level 125 | names from ``.files()`` when a ``top-level.txt`` 126 | (Setuptools-specific metadata) is not present. 127 | 128 | v4.6.4 129 | ====== 130 | 131 | * #334: Correct ``SimplePath`` protocol to match ``pathlib`` 132 | protocol for ``__truediv__``. 133 | 134 | v4.6.3 135 | ====== 136 | 137 | * Moved workaround for #327 to ``_compat`` module. 138 | 139 | v4.6.2 140 | ====== 141 | 142 | * bpo-44784: Avoid errors in test suite when 143 | DeprecationWarnings are treated as errors. 144 | 145 | v4.6.1 146 | ====== 147 | 148 | * #327: Deprecation warnings now honor call stack variance 149 | on PyPy. 150 | 151 | v4.6.0 152 | ====== 153 | 154 | * #326: Performance tests now rely on 155 | `pytest-perf `_. 156 | To disable these tests, which require network access 157 | and a git checkout, pass ``-p no:perf`` to pytest. 158 | 159 | v4.5.0 160 | ====== 161 | 162 | * #319: Remove ``SelectableGroups`` deprecation exception 163 | for flake8. 164 | 165 | v4.4.0 166 | ====== 167 | 168 | * #300: Restore compatibility in the result from 169 | ``Distribution.entry_points`` (``EntryPoints``) to honor 170 | expectations in older implementations and issuing 171 | deprecation warnings for these cases: 172 | 173 | - ``EntryPoints`` objects are once again mutable, allowing 174 | for ``sort()`` and other list-based mutation operations. 175 | Avoid deprecation warnings by casting to a 176 | mutable sequence (e.g. 177 | ``list(dist.entry_points).sort()``). 178 | 179 | - ``EntryPoints`` results once again allow 180 | for access by index. To avoid deprecation warnings, 181 | cast the result to a Sequence first 182 | (e.g. ``tuple(dist.entry_points)[0]``). 183 | 184 | v4.3.1 185 | ====== 186 | 187 | * #320: Fix issue where normalized name for eggs was 188 | incorrectly solicited, leading to metadata being 189 | unavailable for eggs. 190 | 191 | v4.3.0 192 | ====== 193 | 194 | * #317: De-duplication of distributions no longer requires 195 | loading the full metadata for ``PathDistribution`` objects, 196 | entry point loading performance by ~10x. 197 | 198 | v4.2.0 199 | ====== 200 | 201 | * Prefer f-strings to ``.format`` calls. 202 | 203 | v4.1.0 204 | ====== 205 | 206 | * #312: Add support for metadata 2.2 (``Dynamic`` field). 207 | 208 | * #315: Add ``SimplePath`` protocol for interface clarity 209 | in ``PathDistribution``. 210 | 211 | v4.0.1 212 | ====== 213 | 214 | * #306: Clearer guidance about compatibility in readme. 215 | 216 | v4.0.0 217 | ====== 218 | 219 | * #304: ``PackageMetadata`` as returned by ``metadata()`` 220 | and ``Distribution.metadata()`` now provides normalized 221 | metadata honoring PEP 566: 222 | 223 | - If a long description is provided in the payload of the 224 | RFC 822 value, it can be retrieved as the ``Description`` 225 | field. 226 | - Any multi-line values in the metadata will be returned as 227 | such. 228 | - For any multi-line values, line continuation characters 229 | are removed. This backward-incompatible change means 230 | that any projects relying on the RFC 822 line continuation 231 | characters being present must be tolerant to them having 232 | been removed. 233 | - Add a ``json`` property that provides the metadata 234 | converted to a JSON-compatible form per PEP 566. 235 | 236 | 237 | v3.10.1 238 | ======= 239 | 240 | * Minor tweaks from CPython. 241 | 242 | v3.10.0 243 | ======= 244 | 245 | * #295: Internal refactoring to unify section parsing logic. 246 | 247 | v3.9.1 248 | ====== 249 | 250 | * #296: Exclude 'prepare' package. 251 | * #297: Fix ValueError when entry points contains comments. 252 | 253 | v3.9.0 254 | ====== 255 | 256 | * Use of Mapping (dict) interfaces on ``SelectableGroups`` 257 | is now flagged as deprecated. Instead, users are advised 258 | to use the select interface for future compatibility. 259 | 260 | Suppress the warning with this filter: 261 | ``ignore:SelectableGroups dict interface``. 262 | 263 | Or with this invocation in the Python environment: 264 | ``warnings.filterwarnings('ignore', 'SelectableGroups dict interface')``. 265 | 266 | Preferably, switch to the ``select`` interface introduced 267 | in 3.7.0. See the 268 | `entry points documentation `_ and changelog for the 3.6 269 | release below for more detail. 270 | 271 | For some use-cases, especially those that rely on 272 | ``importlib.metadata`` in Python 3.8 and 3.9 or 273 | those relying on older ``importlib_metadata`` (especially 274 | on Python 3.5 and earlier), 275 | `backports.entry_points_selectable `_ 276 | was created to ease the transition. Please have a look 277 | at that project if simply relying on importlib_metadata 3.6+ 278 | is not straightforward. Background in #298. 279 | 280 | * #283: Entry point parsing no longer relies on ConfigParser 281 | and instead uses a custom, one-pass parser to load the 282 | config, resulting in a ~20% performance improvement when 283 | loading entry points. 284 | 285 | v3.8.2 286 | ====== 287 | 288 | * #293: Re-enabled lazy evaluation of path lookup through 289 | a FreezableDefaultDict. 290 | 291 | v3.8.1 292 | ====== 293 | 294 | * #293: Workaround for error in distribution search. 295 | 296 | v3.8.0 297 | ====== 298 | 299 | * #290: Add mtime-based caching for ``FastPath`` and its 300 | lookups, dramatically increasing performance for repeated 301 | distribution lookups. 302 | 303 | v3.7.3 304 | ====== 305 | 306 | * Docs enhancements and cleanup following review in 307 | `GH-24782 `_. 308 | 309 | v3.7.2 310 | ====== 311 | 312 | * Cleaned up cruft in entry_points docstring. 313 | 314 | v3.7.1 315 | ====== 316 | 317 | * Internal refactoring to facilitate ``entry_points() -> dict`` 318 | deprecation. 319 | 320 | v3.7.0 321 | ====== 322 | 323 | * #131: Added ``packages_distributions`` to conveniently 324 | resolve a top-level package or module to its distribution(s). 325 | 326 | v3.6.0 327 | ====== 328 | 329 | * #284: Introduces new ``EntryPoints`` object, a tuple of 330 | ``EntryPoint`` objects but with convenience properties for 331 | selecting and inspecting the results: 332 | 333 | - ``.select()`` accepts ``group`` or ``name`` keyword 334 | parameters and returns a new ``EntryPoints`` tuple 335 | with only those that match the selection. 336 | - ``.groups`` property presents all of the group names. 337 | - ``.names`` property presents the names of the entry points. 338 | - Item access (e.g. ``eps[name]``) retrieves a single 339 | entry point by name. 340 | 341 | ``entry_points`` now accepts "selection parameters", 342 | same as ``EntryPoint.select()``. 343 | 344 | ``entry_points()`` now provides a future-compatible 345 | ``SelectableGroups`` object that supplies the above interface 346 | (except item access) but remains a dict for compatibility. 347 | 348 | In the future, ``entry_points()`` will return an 349 | ``EntryPoints`` object for all entry points. 350 | 351 | If passing selection parameters to ``entry_points``, the 352 | future behavior is invoked and an ``EntryPoints`` is the 353 | result. 354 | 355 | * #284: Construction of entry points using 356 | ``dict([EntryPoint, ...])`` is now deprecated and raises 357 | an appropriate DeprecationWarning and will be removed in 358 | a future version. 359 | 360 | * #300: ``Distribution.entry_points`` now presents as an 361 | ``EntryPoints`` object and access by index is no longer 362 | allowed. If access by index is required, cast to a sequence 363 | first. 364 | 365 | v3.5.0 366 | ====== 367 | 368 | * #280: ``entry_points`` now only returns entry points for 369 | unique distributions (by name). 370 | 371 | v3.4.0 372 | ====== 373 | 374 | * #10: Project now declares itself as being typed. 375 | * #272: Additional performance enhancements to distribution 376 | discovery. 377 | * #111: For PyPA projects, add test ensuring that 378 | ``MetadataPathFinder._search_paths`` honors the needed 379 | interface. Method is still private. 380 | 381 | v3.3.0 382 | ====== 383 | 384 | * #265: ``EntryPoint`` objects now expose a ``.dist`` object 385 | referencing the ``Distribution`` when constructed from a 386 | Distribution. 387 | 388 | v3.2.0 389 | ====== 390 | 391 | * The object returned by ``metadata()`` now has a 392 | formally-defined protocol called ``PackageMetadata`` 393 | with declared support for the ``.get_all()`` method. 394 | Fixes #126. 395 | 396 | v3.1.1 397 | ====== 398 | 399 | v2.1.1 400 | ====== 401 | 402 | * #261: Restored compatibility for package discovery for 403 | metadata without version in the name and for legacy 404 | eggs. 405 | 406 | v3.1.0 407 | ====== 408 | 409 | * Merge with 2.1.0. 410 | 411 | v2.1.0 412 | ====== 413 | 414 | * #253: When querying for package metadata, the lookup 415 | now honors 416 | `package normalization rules `_. 417 | 418 | v3.0.0 419 | ====== 420 | 421 | * Require Python 3.6 or later. 422 | 423 | v2.0.0 424 | ====== 425 | 426 | * ``importlib_metadata`` no longer presents a 427 | ``__version__`` attribute. Consumers wishing to 428 | resolve the version of the package should query it 429 | directly with 430 | ``importlib_metadata.version('importlib-metadata')``. 431 | Closes #71. 432 | 433 | v1.7.0 434 | ====== 435 | 436 | * ``PathNotFoundError`` now has a custom ``__str__`` 437 | mentioning "package metadata" being missing to help 438 | guide users to the cause when the package is installed 439 | but no metadata is present. Closes #124. 440 | 441 | v1.6.1 442 | ====== 443 | 444 | * Added ``Distribution._local()`` as a provisional 445 | demonstration of how to load metadata for a local 446 | package. Implicitly requires that 447 | `pep517 `_ is 448 | installed. Ref #42. 449 | * Ensure inputs to FastPath are Unicode. Closes #121. 450 | * Tests now rely on ``importlib.resources.files`` (and 451 | backport) instead of the older ``path`` function. 452 | * Support any iterable from ``find_distributions``. 453 | Closes #122. 454 | 455 | v1.6.0 456 | ====== 457 | 458 | * Added ``module`` and ``attr`` attributes to ``EntryPoint`` 459 | 460 | v1.5.2 461 | ====== 462 | 463 | * Fix redundant entries from ``FastPath.zip_children``. 464 | Closes #117. 465 | 466 | v1.5.1 467 | ====== 468 | 469 | * Improve reliability and consistency of compatibility 470 | imports for contextlib and pathlib when running tests. 471 | Closes #116. 472 | 473 | v1.5.0 474 | ====== 475 | 476 | * Additional performance optimizations in FastPath now 477 | saves an additional 20% on a typical call. 478 | * Correct for issue where PyOxidizer finder has no 479 | ``__module__`` attribute. Closes #110. 480 | 481 | v1.4.0 482 | ====== 483 | 484 | * Through careful optimization, ``distribution()`` is 485 | 3-4x faster. Thanks to Antony Lee for the 486 | contribution. Closes #95. 487 | 488 | * When searching through ``sys.path``, if any error 489 | occurs attempting to list a path entry, that entry 490 | is skipped, making the system much more lenient 491 | to errors. Closes #94. 492 | 493 | v1.3.0 494 | ====== 495 | 496 | * Improve custom finders documentation. Closes #105. 497 | 498 | v1.2.0 499 | ====== 500 | 501 | * Once again, drop support for Python 3.4. Ref #104. 502 | 503 | v1.1.3 504 | ====== 505 | 506 | * Restored support for Python 3.4 due to improper version 507 | compatibility declarations in the v1.1.0 and v1.1.1 508 | releases. Closes #104. 509 | 510 | v1.1.2 511 | ====== 512 | 513 | * Repaired project metadata to correctly declare the 514 | ``python_requires`` directive. Closes #103. 515 | 516 | v1.1.1 517 | ====== 518 | 519 | * Fixed ``repr(EntryPoint)`` on PyPy 3 also. Closes #102. 520 | 521 | v1.1.0 522 | ====== 523 | 524 | * Dropped support for Python 3.4. 525 | * EntryPoints are now pickleable. Closes #96. 526 | * Fixed ``repr(EntryPoint)`` on PyPy 2. Closes #97. 527 | 528 | v1.0.0 529 | ====== 530 | 531 | * Project adopts semver for versioning. 532 | 533 | * Removed compatibility shim introduced in 0.23. 534 | 535 | * For better compatibility with the stdlib implementation and to 536 | avoid the same distributions being discovered by the stdlib and 537 | backport implementations, the backport now disables the 538 | stdlib DistributionFinder during initialization (import time). 539 | Closes #91 and closes #100. 540 | 541 | 0.23 542 | ==== 543 | 544 | * Added a compatibility shim to prevent failures on beta releases 545 | of Python before the signature changed to accept the 546 | "context" parameter on find_distributions. This workaround 547 | will have a limited lifespan, not to extend beyond release of 548 | Python 3.8 final. 549 | 550 | 0.22 551 | ==== 552 | 553 | * Renamed ``package`` parameter to ``distribution_name`` 554 | as `recommended `_ 555 | in the following functions: ``distribution``, ``metadata``, 556 | ``version``, ``files``, and ``requires``. This 557 | backward-incompatible change is expected to have little impact 558 | as these functions are assumed to be primarily used with 559 | positional parameters. 560 | 561 | 0.21 562 | ==== 563 | 564 | * ``importlib.metadata`` now exposes the ``DistributionFinder`` 565 | metaclass and references it in the docs for extending the 566 | search algorithm. 567 | * Add ``Distribution.at`` for constructing a Distribution object 568 | from a known metadata directory on the file system. Closes #80. 569 | * Distribution finders now receive a context object that 570 | supplies ``.path`` and ``.name`` properties. This change 571 | introduces a fundamental backward incompatibility for 572 | any projects implementing a ``find_distributions`` method 573 | on a ``MetaPathFinder``. This new layer of abstraction 574 | allows this context to be supplied directly or constructed 575 | on demand and opens the opportunity for a 576 | ``find_distributions`` method to solicit additional 577 | context from the caller. Closes #85. 578 | 579 | 0.20 580 | ==== 581 | 582 | * Clarify in the docs that calls to ``.files`` could return 583 | ``None`` when the metadata is not present. Closes #69. 584 | * Return all requirements and not just the first for dist-info 585 | packages. Closes #67. 586 | 587 | 0.19 588 | ==== 589 | 590 | * Restrain over-eager egg metadata resolution. 591 | * Add support for entry points with colons in the name. Closes #75. 592 | 593 | 0.18 594 | ==== 595 | 596 | * Parse entry points case sensitively. Closes #68 597 | * Add a version constraint on the backport configparser package. Closes #66 598 | 599 | 0.17 600 | ==== 601 | 602 | * Fix a permission problem in the tests on Windows. 603 | 604 | 0.16 605 | ==== 606 | 607 | * Don't crash if there exists an EGG-INFO directory on sys.path. 608 | 609 | 0.15 610 | ==== 611 | 612 | * Fix documentation. 613 | 614 | 0.14 615 | ==== 616 | 617 | * Removed ``local_distribution`` function from the API. 618 | **This backward-incompatible change removes this 619 | behavior summarily**. Projects should remove their 620 | reliance on this behavior. A replacement behavior is 621 | under review in the `pep517 project 622 | `_. Closes #42. 623 | 624 | 0.13 625 | ==== 626 | 627 | * Update docstrings to match PEP 8. Closes #63. 628 | * Merged modules into one module. Closes #62. 629 | 630 | 0.12 631 | ==== 632 | 633 | * Add support for eggs. !65; Closes #19. 634 | 635 | 0.11 636 | ==== 637 | 638 | * Support generic zip files (not just wheels). Closes #59 639 | * Support zip files with multiple distributions in them. Closes #60 640 | * Fully expose the public API in ``importlib_metadata.__all__``. 641 | 642 | 0.10 643 | ==== 644 | 645 | * The ``Distribution`` ABC is now officially part of the public API. 646 | Closes #37. 647 | * Fixed support for older single file egg-info formats. Closes #43. 648 | * Fixed a testing bug when ``$CWD`` has spaces in the path. Closes #50. 649 | * Add Python 3.8 to the ``tox`` testing matrix. 650 | 651 | 0.9 652 | === 653 | 654 | * Fixed issue where entry points without an attribute would raise an 655 | Exception. Closes #40. 656 | * Removed unused ``name`` parameter from ``entry_points()``. Closes #44. 657 | * ``DistributionFinder`` classes must now be instantiated before 658 | being placed on ``sys.meta_path``. 659 | 660 | 0.8 661 | === 662 | 663 | * This library can now discover/enumerate all installed packages. **This 664 | backward-incompatible change alters the protocol finders must 665 | implement to support distribution package discovery.** Closes #24. 666 | * The signature of ``find_distributions()`` on custom installer finders 667 | should now accept two parameters, ``name`` and ``path`` and 668 | these parameters must supply defaults. 669 | * The ``entry_points()`` method no longer accepts a package name 670 | but instead returns all entry points in a dictionary keyed by the 671 | ``EntryPoint.group``. The ``resolve`` method has been removed. Instead, 672 | call ``EntryPoint.load()``, which has the same semantics as 673 | ``pkg_resources`` and ``entrypoints``. **This is a backward incompatible 674 | change.** 675 | * Metadata is now always returned as Unicode text regardless of 676 | Python version. Closes #29. 677 | * This library can now discover metadata for a 'local' package (found 678 | in the current-working directory). Closes #27. 679 | * Added ``files()`` function for resolving files from a distribution. 680 | * Added a new ``requires()`` function, which returns the requirements 681 | for a package suitable for parsing by 682 | ``packaging.requirements.Requirement``. Closes #18. 683 | * The top-level ``read_text()`` function has been removed. Use 684 | ``PackagePath.read_text()`` on instances returned by the ``files()`` 685 | function. **This is a backward incompatible change.** 686 | * Release dates are now automatically injected into the changelog 687 | based on SCM tags. 688 | 689 | 0.7 690 | === 691 | 692 | * Fixed issue where packages with dashes in their names would 693 | not be discovered. Closes #21. 694 | * Distribution lookup is now case-insensitive. Closes #20. 695 | * Wheel distributions can no longer be discovered by their module 696 | name. Like Path distributions, they must be indicated by their 697 | distribution package name. 698 | 699 | 0.6 700 | === 701 | 702 | * Removed ``importlib_metadata.distribution`` function. Now 703 | the public interface is primarily the utility functions exposed 704 | in ``importlib_metadata.__all__``. Closes #14. 705 | * Added two new utility functions ``read_text`` and 706 | ``metadata``. 707 | 708 | 0.5 709 | === 710 | 711 | * Updated README and removed details about Distribution 712 | class, now considered private. Closes #15. 713 | * Added test suite support for Python 3.4+. 714 | * Fixed SyntaxErrors on Python 3.4 and 3.5. !12 715 | * Fixed errors on Windows joining Path elements. !15 716 | 717 | 0.4 718 | === 719 | 720 | * Housekeeping. 721 | 722 | 0.3 723 | === 724 | 725 | * Added usage documentation. Closes #8 726 | * Add support for getting metadata from wheels on ``sys.path``. Closes #9 727 | 728 | 0.2 729 | === 730 | 731 | * Added ``importlib_metadata.entry_points()``. Closes #1 732 | * Added ``importlib_metadata.resolve()``. Closes #12 733 | * Add support for Python 2.7. Closes #4 734 | 735 | 0.1 736 | === 737 | 738 | * Initial release. 739 | 740 | 741 | .. 742 | Local Variables: 743 | mode: change-log-mode 744 | indent-tabs-mode: nil 745 | sentence-end-double-space: t 746 | fill-column: 78 747 | coding: utf-8 748 | End: 749 | -------------------------------------------------------------------------------- /vendor/importlib_metadata/LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /vendor/importlib_metadata/README.rst: -------------------------------------------------------------------------------- 1 | .. image:: https://img.shields.io/pypi/v/importlib_metadata.svg 2 | :target: `PyPI link`_ 3 | 4 | .. image:: https://img.shields.io/pypi/pyversions/importlib_metadata.svg 5 | :target: `PyPI link`_ 6 | 7 | .. _PyPI link: https://pypi.org/project/importlib_metadata 8 | 9 | .. image:: https://github.com/python/importlib_metadata/workflows/tests/badge.svg 10 | :target: https://github.com/python/importlib_metadata/actions?query=workflow%3A%22tests%22 11 | :alt: tests 12 | 13 | .. image:: https://img.shields.io/badge/code%20style-black-000000.svg 14 | :target: https://github.com/psf/black 15 | :alt: Code style: Black 16 | 17 | .. image:: https://readthedocs.org/projects/importlib-metadata/badge/?version=latest 18 | :target: https://importlib-metadata.readthedocs.io/en/latest/?badge=latest 19 | 20 | .. image:: https://img.shields.io/badge/skeleton-2022-informational 21 | :target: https://blog.jaraco.com/skeleton 22 | 23 | .. image:: https://tidelift.com/badges/package/pypi/importlib-metadata 24 | :target: https://tidelift.com/subscription/pkg/pypi-importlib-metadata?utm_source=pypi-importlib-metadata&utm_medium=readme 25 | 26 | Library to access the metadata for a Python package. 27 | 28 | This package supplies third-party access to the functionality of 29 | `importlib.metadata `_ 30 | including improvements added to subsequent Python versions. 31 | 32 | 33 | Compatibility 34 | ============= 35 | 36 | New features are introduced in this third-party library and later merged 37 | into CPython. The following table indicates which versions of this library 38 | were contributed to different versions in the standard library: 39 | 40 | .. list-table:: 41 | :header-rows: 1 42 | 43 | * - importlib_metadata 44 | - stdlib 45 | * - 4.8 46 | - 3.11 47 | * - 4.4 48 | - 3.10 49 | * - 1.4 50 | - 3.8 51 | 52 | 53 | Usage 54 | ===== 55 | 56 | See the `online documentation `_ 57 | for usage details. 58 | 59 | `Finder authors 60 | `_ can 61 | also add support for custom package installers. See the above documentation 62 | for details. 63 | 64 | 65 | Caveats 66 | ======= 67 | 68 | This project primarily supports third-party packages installed by PyPA 69 | tools (or other conforming packages). It does not support: 70 | 71 | - Packages in the stdlib. 72 | - Packages installed without metadata. 73 | 74 | Project details 75 | =============== 76 | 77 | * Project home: https://github.com/python/importlib_metadata 78 | * Report bugs at: https://github.com/python/importlib_metadata/issues 79 | * Code hosting: https://github.com/python/importlib_metadata 80 | * Documentation: https://importlib_metadata.readthedocs.io/ 81 | 82 | For Enterprise 83 | ============== 84 | 85 | Available as part of the Tidelift Subscription. 86 | 87 | This project and the maintainers of thousands of other packages are working with Tidelift to deliver one enterprise subscription that covers all of the open source you use. 88 | 89 | `Learn more `_. 90 | 91 | Security Contact 92 | ================ 93 | 94 | To report a security vulnerability, please use the 95 | `Tidelift security contact `_. 96 | Tidelift will coordinate the fix and disclosure. 97 | -------------------------------------------------------------------------------- /vendor/importlib_metadata/importlib_metadata/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | import abc 4 | import csv 5 | import sys 6 | import zipp 7 | import email 8 | import pathlib 9 | import operator 10 | import textwrap 11 | import warnings 12 | import functools 13 | import itertools 14 | import posixpath 15 | import collections 16 | 17 | from . import _adapters, _meta, _py39compat 18 | from ._collections import FreezableDefaultDict, Pair 19 | from ._compat import ( 20 | NullFinder, 21 | install, 22 | pypy_partial, 23 | ) 24 | from ._functools import method_cache, pass_none 25 | from ._itertools import always_iterable, unique_everseen 26 | from ._meta import PackageMetadata, SimplePath 27 | 28 | from contextlib import suppress 29 | from importlib import import_module 30 | from importlib.abc import MetaPathFinder 31 | from itertools import starmap 32 | from typing import List, Mapping, Optional 33 | 34 | 35 | __all__ = [ 36 | 'Distribution', 37 | 'DistributionFinder', 38 | 'PackageMetadata', 39 | 'PackageNotFoundError', 40 | 'distribution', 41 | 'distributions', 42 | 'entry_points', 43 | 'files', 44 | 'metadata', 45 | 'packages_distributions', 46 | 'requires', 47 | 'version', 48 | ] 49 | 50 | 51 | class PackageNotFoundError(ModuleNotFoundError): 52 | """The package was not found.""" 53 | 54 | def __str__(self): 55 | return f"No package metadata was found for {self.name}" 56 | 57 | @property 58 | def name(self): 59 | (name,) = self.args 60 | return name 61 | 62 | 63 | class Sectioned: 64 | """ 65 | A simple entry point config parser for performance 66 | 67 | >>> for item in Sectioned.read(Sectioned._sample): 68 | ... print(item) 69 | Pair(name='sec1', value='# comments ignored') 70 | Pair(name='sec1', value='a = 1') 71 | Pair(name='sec1', value='b = 2') 72 | Pair(name='sec2', value='a = 2') 73 | 74 | >>> res = Sectioned.section_pairs(Sectioned._sample) 75 | >>> item = next(res) 76 | >>> item.name 77 | 'sec1' 78 | >>> item.value 79 | Pair(name='a', value='1') 80 | >>> item = next(res) 81 | >>> item.value 82 | Pair(name='b', value='2') 83 | >>> item = next(res) 84 | >>> item.name 85 | 'sec2' 86 | >>> item.value 87 | Pair(name='a', value='2') 88 | >>> list(res) 89 | [] 90 | """ 91 | 92 | _sample = textwrap.dedent( 93 | """ 94 | [sec1] 95 | # comments ignored 96 | a = 1 97 | b = 2 98 | 99 | [sec2] 100 | a = 2 101 | """ 102 | ).lstrip() 103 | 104 | @classmethod 105 | def section_pairs(cls, text): 106 | return ( 107 | section._replace(value=Pair.parse(section.value)) 108 | for section in cls.read(text, filter_=cls.valid) 109 | if section.name is not None 110 | ) 111 | 112 | @staticmethod 113 | def read(text, filter_=None): 114 | lines = filter(filter_, map(str.strip, text.splitlines())) 115 | name = None 116 | for value in lines: 117 | section_match = value.startswith('[') and value.endswith(']') 118 | if section_match: 119 | name = value.strip('[]') 120 | continue 121 | yield Pair(name, value) 122 | 123 | @staticmethod 124 | def valid(line): 125 | return line and not line.startswith('#') 126 | 127 | 128 | class DeprecatedTuple: 129 | """ 130 | Provide subscript item access for backward compatibility. 131 | 132 | >>> recwarn = getfixture('recwarn') 133 | >>> ep = EntryPoint(name='name', value='value', group='group') 134 | >>> ep[:] 135 | ('name', 'value', 'group') 136 | >>> ep[0] 137 | 'name' 138 | >>> len(recwarn) 139 | 1 140 | """ 141 | 142 | _warn = functools.partial( 143 | warnings.warn, 144 | "EntryPoint tuple interface is deprecated. Access members by name.", 145 | DeprecationWarning, 146 | stacklevel=pypy_partial(2), 147 | ) 148 | 149 | def __getitem__(self, item): 150 | self._warn() 151 | return self._key()[item] 152 | 153 | 154 | class EntryPoint(DeprecatedTuple): 155 | """An entry point as defined by Python packaging conventions. 156 | 157 | See `the packaging docs on entry points 158 | `_ 159 | for more information. 160 | 161 | >>> ep = EntryPoint( 162 | ... name=None, group=None, value='package.module:attr [extra1, extra2]') 163 | >>> ep.module 164 | 'package.module' 165 | >>> ep.attr 166 | 'attr' 167 | >>> ep.extras 168 | ['extra1', 'extra2'] 169 | """ 170 | 171 | pattern = re.compile( 172 | r'(?P[\w.]+)\s*' 173 | r'(:\s*(?P[\w.]+)\s*)?' 174 | r'((?P\[.*\])\s*)?$' 175 | ) 176 | """ 177 | A regular expression describing the syntax for an entry point, 178 | which might look like: 179 | 180 | - module 181 | - package.module 182 | - package.module:attribute 183 | - package.module:object.attribute 184 | - package.module:attr [extra1, extra2] 185 | 186 | Other combinations are possible as well. 187 | 188 | The expression is lenient about whitespace around the ':', 189 | following the attr, and following any extras. 190 | """ 191 | 192 | name: str 193 | value: str 194 | group: str 195 | 196 | dist: Optional['Distribution'] = None 197 | 198 | def __init__(self, name, value, group): 199 | vars(self).update(name=name, value=value, group=group) 200 | 201 | def load(self): 202 | """Load the entry point from its definition. If only a module 203 | is indicated by the value, return that module. Otherwise, 204 | return the named object. 205 | """ 206 | match = self.pattern.match(self.value) 207 | module = import_module(match.group('module')) 208 | attrs = filter(None, (match.group('attr') or '').split('.')) 209 | return functools.reduce(getattr, attrs, module) 210 | 211 | @property 212 | def module(self): 213 | match = self.pattern.match(self.value) 214 | return match.group('module') 215 | 216 | @property 217 | def attr(self): 218 | match = self.pattern.match(self.value) 219 | return match.group('attr') 220 | 221 | @property 222 | def extras(self): 223 | match = self.pattern.match(self.value) 224 | return re.findall(r'\w+', match.group('extras') or '') 225 | 226 | def _for(self, dist): 227 | vars(self).update(dist=dist) 228 | return self 229 | 230 | def matches(self, **params): 231 | """ 232 | EntryPoint matches the given parameters. 233 | 234 | >>> ep = EntryPoint(group='foo', name='bar', value='bing:bong [extra1, extra2]') 235 | >>> ep.matches(group='foo') 236 | True 237 | >>> ep.matches(name='bar', value='bing:bong [extra1, extra2]') 238 | True 239 | >>> ep.matches(group='foo', name='other') 240 | False 241 | >>> ep.matches() 242 | True 243 | >>> ep.matches(extras=['extra1', 'extra2']) 244 | True 245 | >>> ep.matches(module='bing') 246 | True 247 | >>> ep.matches(attr='bong') 248 | True 249 | """ 250 | attrs = (getattr(self, param) for param in params) 251 | return all(map(operator.eq, params.values(), attrs)) 252 | 253 | def _key(self): 254 | return self.name, self.value, self.group 255 | 256 | def __lt__(self, other): 257 | return self._key() < other._key() 258 | 259 | def __eq__(self, other): 260 | return self._key() == other._key() 261 | 262 | def __setattr__(self, name, value): 263 | raise AttributeError("EntryPoint objects are immutable.") 264 | 265 | def __repr__(self): 266 | return ( 267 | f'EntryPoint(name={self.name!r}, value={self.value!r}, ' 268 | f'group={self.group!r})' 269 | ) 270 | 271 | def __hash__(self): 272 | return hash(self._key()) 273 | 274 | 275 | class EntryPoints(tuple): 276 | """ 277 | An immutable collection of selectable EntryPoint objects. 278 | """ 279 | 280 | __slots__ = () 281 | 282 | def __getitem__(self, name): # -> EntryPoint: 283 | """ 284 | Get the EntryPoint in self matching name. 285 | """ 286 | try: 287 | return next(iter(self.select(name=name))) 288 | except StopIteration: 289 | raise KeyError(name) 290 | 291 | def select(self, **params): 292 | """ 293 | Select entry points from self that match the 294 | given parameters (typically group and/or name). 295 | """ 296 | candidates = (_py39compat.ep_matches(ep, **params) for ep in self) 297 | return EntryPoints(ep for ep, predicate in candidates if predicate) 298 | 299 | @property 300 | def names(self): 301 | """ 302 | Return the set of all names of all entry points. 303 | """ 304 | return {ep.name for ep in self} 305 | 306 | @property 307 | def groups(self): 308 | """ 309 | Return the set of all groups of all entry points. 310 | """ 311 | return {ep.group for ep in self} 312 | 313 | @classmethod 314 | def _from_text_for(cls, text, dist): 315 | return cls(ep._for(dist) for ep in cls._from_text(text)) 316 | 317 | @staticmethod 318 | def _from_text(text): 319 | return ( 320 | EntryPoint(name=item.value.name, value=item.value.value, group=item.name) 321 | for item in Sectioned.section_pairs(text or '') 322 | ) 323 | 324 | 325 | class PackagePath(pathlib.PurePosixPath): 326 | """A reference to a path in a package""" 327 | 328 | def read_text(self, encoding='utf-8'): 329 | with self.locate().open(encoding=encoding) as stream: 330 | return stream.read() 331 | 332 | def read_binary(self): 333 | with self.locate().open('rb') as stream: 334 | return stream.read() 335 | 336 | def locate(self): 337 | """Return a path-like object for this path""" 338 | return self.dist.locate_file(self) 339 | 340 | 341 | class FileHash: 342 | def __init__(self, spec): 343 | self.mode, _, self.value = spec.partition('=') 344 | 345 | def __repr__(self): 346 | return f'' 347 | 348 | 349 | class Distribution: 350 | """A Python distribution package.""" 351 | 352 | @abc.abstractmethod 353 | def read_text(self, filename): 354 | """Attempt to load metadata file given by the name. 355 | 356 | :param filename: The name of the file in the distribution info. 357 | :return: The text if found, otherwise None. 358 | """ 359 | 360 | @abc.abstractmethod 361 | def locate_file(self, path): 362 | """ 363 | Given a path to a file in this distribution, return a path 364 | to it. 365 | """ 366 | 367 | @classmethod 368 | def from_name(cls, name: str): 369 | """Return the Distribution for the given package name. 370 | 371 | :param name: The name of the distribution package to search for. 372 | :return: The Distribution instance (or subclass thereof) for the named 373 | package, if found. 374 | :raises PackageNotFoundError: When the named package's distribution 375 | metadata cannot be found. 376 | :raises ValueError: When an invalid value is supplied for name. 377 | """ 378 | if not name: 379 | raise ValueError("A distribution name is required.") 380 | try: 381 | return next(cls.discover(name=name)) 382 | except StopIteration: 383 | raise PackageNotFoundError(name) 384 | 385 | @classmethod 386 | def discover(cls, **kwargs): 387 | """Return an iterable of Distribution objects for all packages. 388 | 389 | Pass a ``context`` or pass keyword arguments for constructing 390 | a context. 391 | 392 | :context: A ``DistributionFinder.Context`` object. 393 | :return: Iterable of Distribution objects for all packages. 394 | """ 395 | context = kwargs.pop('context', None) 396 | if context and kwargs: 397 | raise ValueError("cannot accept context and kwargs") 398 | context = context or DistributionFinder.Context(**kwargs) 399 | return itertools.chain.from_iterable( 400 | resolver(context) for resolver in cls._discover_resolvers() 401 | ) 402 | 403 | @staticmethod 404 | def at(path): 405 | """Return a Distribution for the indicated metadata path 406 | 407 | :param path: a string or path-like object 408 | :return: a concrete Distribution instance for the path 409 | """ 410 | return PathDistribution(pathlib.Path(path)) 411 | 412 | @staticmethod 413 | def _discover_resolvers(): 414 | """Search the meta_path for resolvers.""" 415 | declared = ( 416 | getattr(finder, 'find_distributions', None) for finder in sys.meta_path 417 | ) 418 | return filter(None, declared) 419 | 420 | @property 421 | def metadata(self) -> _meta.PackageMetadata: 422 | """Return the parsed metadata for this Distribution. 423 | 424 | The returned object will have keys that name the various bits of 425 | metadata. See PEP 566 for details. 426 | """ 427 | text = ( 428 | self.read_text('METADATA') 429 | or self.read_text('PKG-INFO') 430 | # This last clause is here to support old egg-info files. Its 431 | # effect is to just end up using the PathDistribution's self._path 432 | # (which points to the egg-info file) attribute unchanged. 433 | or self.read_text('') 434 | ) 435 | return _adapters.Message(email.message_from_string(text)) 436 | 437 | @property 438 | def name(self): 439 | """Return the 'Name' metadata for the distribution package.""" 440 | return self.metadata['Name'] 441 | 442 | @property 443 | def _normalized_name(self): 444 | """Return a normalized version of the name.""" 445 | return Prepared.normalize(self.name) 446 | 447 | @property 448 | def version(self): 449 | """Return the 'Version' metadata for the distribution package.""" 450 | return self.metadata['Version'] 451 | 452 | @property 453 | def entry_points(self): 454 | return EntryPoints._from_text_for(self.read_text('entry_points.txt'), self) 455 | 456 | @property 457 | def files(self): 458 | """Files in this distribution. 459 | 460 | :return: List of PackagePath for this distribution or None 461 | 462 | Result is `None` if the metadata file that enumerates files 463 | (i.e. RECORD for dist-info or SOURCES.txt for egg-info) is 464 | missing. 465 | Result may be empty if the metadata exists but is empty. 466 | """ 467 | 468 | def make_file(name, hash=None, size_str=None): 469 | result = PackagePath(name) 470 | result.hash = FileHash(hash) if hash else None 471 | result.size = int(size_str) if size_str else None 472 | result.dist = self 473 | return result 474 | 475 | @pass_none 476 | def make_files(lines): 477 | return list(starmap(make_file, csv.reader(lines))) 478 | 479 | return make_files(self._read_files_distinfo() or self._read_files_egginfo()) 480 | 481 | def _read_files_distinfo(self): 482 | """ 483 | Read the lines of RECORD 484 | """ 485 | text = self.read_text('RECORD') 486 | return text and text.splitlines() 487 | 488 | def _read_files_egginfo(self): 489 | """ 490 | SOURCES.txt might contain literal commas, so wrap each line 491 | in quotes. 492 | """ 493 | text = self.read_text('SOURCES.txt') 494 | return text and map('"{}"'.format, text.splitlines()) 495 | 496 | @property 497 | def requires(self): 498 | """Generated requirements specified for this Distribution""" 499 | reqs = self._read_dist_info_reqs() or self._read_egg_info_reqs() 500 | return reqs and list(reqs) 501 | 502 | def _read_dist_info_reqs(self): 503 | return self.metadata.get_all('Requires-Dist') 504 | 505 | def _read_egg_info_reqs(self): 506 | source = self.read_text('requires.txt') 507 | return pass_none(self._deps_from_requires_text)(source) 508 | 509 | @classmethod 510 | def _deps_from_requires_text(cls, source): 511 | return cls._convert_egg_info_reqs_to_simple_reqs(Sectioned.read(source)) 512 | 513 | @staticmethod 514 | def _convert_egg_info_reqs_to_simple_reqs(sections): 515 | """ 516 | Historically, setuptools would solicit and store 'extra' 517 | requirements, including those with environment markers, 518 | in separate sections. More modern tools expect each 519 | dependency to be defined separately, with any relevant 520 | extras and environment markers attached directly to that 521 | requirement. This method converts the former to the 522 | latter. See _test_deps_from_requires_text for an example. 523 | """ 524 | 525 | def make_condition(name): 526 | return name and f'extra == "{name}"' 527 | 528 | def quoted_marker(section): 529 | section = section or '' 530 | extra, sep, markers = section.partition(':') 531 | if extra and markers: 532 | markers = f'({markers})' 533 | conditions = list(filter(None, [markers, make_condition(extra)])) 534 | return '; ' + ' and '.join(conditions) if conditions else '' 535 | 536 | def url_req_space(req): 537 | """ 538 | PEP 508 requires a space between the url_spec and the quoted_marker. 539 | Ref python/importlib_metadata#357. 540 | """ 541 | # '@' is uniquely indicative of a url_req. 542 | return ' ' * ('@' in req) 543 | 544 | for section in sections: 545 | space = url_req_space(section.value) 546 | yield section.value + space + quoted_marker(section.name) 547 | 548 | 549 | class DistributionFinder(MetaPathFinder): 550 | """ 551 | A MetaPathFinder capable of discovering installed distributions. 552 | """ 553 | 554 | class Context: 555 | """ 556 | Keyword arguments presented by the caller to 557 | ``distributions()`` or ``Distribution.discover()`` 558 | to narrow the scope of a search for distributions 559 | in all DistributionFinders. 560 | 561 | Each DistributionFinder may expect any parameters 562 | and should attempt to honor the canonical 563 | parameters defined below when appropriate. 564 | """ 565 | 566 | name = None 567 | """ 568 | Specific name for which a distribution finder should match. 569 | A name of ``None`` matches all distributions. 570 | """ 571 | 572 | def __init__(self, **kwargs): 573 | vars(self).update(kwargs) 574 | 575 | @property 576 | def path(self): 577 | """ 578 | The sequence of directory path that a distribution finder 579 | should search. 580 | 581 | Typically refers to Python installed package paths such as 582 | "site-packages" directories and defaults to ``sys.path``. 583 | """ 584 | return vars(self).get('path', sys.path) 585 | 586 | @abc.abstractmethod 587 | def find_distributions(self, context=Context()): 588 | """ 589 | Find distributions. 590 | 591 | Return an iterable of all Distribution instances capable of 592 | loading the metadata for packages matching the ``context``, 593 | a DistributionFinder.Context instance. 594 | """ 595 | 596 | 597 | class FastPath: 598 | """ 599 | Micro-optimized class for searching a path for 600 | children. 601 | 602 | >>> FastPath('').children() 603 | ['...'] 604 | """ 605 | 606 | @functools.lru_cache() # type: ignore 607 | def __new__(cls, root): 608 | return super().__new__(cls) 609 | 610 | def __init__(self, root): 611 | self.root = root 612 | 613 | def joinpath(self, child): 614 | return pathlib.Path(self.root, child) 615 | 616 | def children(self): 617 | with suppress(Exception): 618 | return os.listdir(self.root or '.') 619 | with suppress(Exception): 620 | return self.zip_children() 621 | return [] 622 | 623 | def zip_children(self): 624 | zip_path = zipp.Path(self.root) 625 | names = zip_path.root.namelist() 626 | self.joinpath = zip_path.joinpath 627 | 628 | return dict.fromkeys(child.split(posixpath.sep, 1)[0] for child in names) 629 | 630 | def search(self, name): 631 | return self.lookup(self.mtime).search(name) 632 | 633 | @property 634 | def mtime(self): 635 | with suppress(OSError): 636 | return os.stat(self.root).st_mtime 637 | self.lookup.cache_clear() 638 | 639 | @method_cache 640 | def lookup(self, mtime): 641 | return Lookup(self) 642 | 643 | 644 | class Lookup: 645 | def __init__(self, path: FastPath): 646 | base = os.path.basename(path.root).lower() 647 | base_is_egg = base.endswith(".egg") 648 | self.infos = FreezableDefaultDict(list) 649 | self.eggs = FreezableDefaultDict(list) 650 | 651 | for child in path.children(): 652 | low = child.lower() 653 | if low.endswith((".dist-info", ".egg-info")): 654 | # rpartition is faster than splitext and suitable for this purpose. 655 | name = low.rpartition(".")[0].partition("-")[0] 656 | normalized = Prepared.normalize(name) 657 | self.infos[normalized].append(path.joinpath(child)) 658 | elif base_is_egg and low == "egg-info": 659 | name = base.rpartition(".")[0].partition("-")[0] 660 | legacy_normalized = Prepared.legacy_normalize(name) 661 | self.eggs[legacy_normalized].append(path.joinpath(child)) 662 | 663 | self.infos.freeze() 664 | self.eggs.freeze() 665 | 666 | def search(self, prepared): 667 | infos = ( 668 | self.infos[prepared.normalized] 669 | if prepared 670 | else itertools.chain.from_iterable(self.infos.values()) 671 | ) 672 | eggs = ( 673 | self.eggs[prepared.legacy_normalized] 674 | if prepared 675 | else itertools.chain.from_iterable(self.eggs.values()) 676 | ) 677 | return itertools.chain(infos, eggs) 678 | 679 | 680 | class Prepared: 681 | """ 682 | A prepared search for metadata on a possibly-named package. 683 | """ 684 | 685 | normalized = None 686 | legacy_normalized = None 687 | 688 | def __init__(self, name): 689 | self.name = name 690 | if name is None: 691 | return 692 | self.normalized = self.normalize(name) 693 | self.legacy_normalized = self.legacy_normalize(name) 694 | 695 | @staticmethod 696 | def normalize(name): 697 | """ 698 | PEP 503 normalization plus dashes as underscores. 699 | """ 700 | return re.sub(r"[-_.]+", "-", name).lower().replace('-', '_') 701 | 702 | @staticmethod 703 | def legacy_normalize(name): 704 | """ 705 | Normalize the package name as found in the convention in 706 | older packaging tools versions and specs. 707 | """ 708 | return name.lower().replace('-', '_') 709 | 710 | def __bool__(self): 711 | return bool(self.name) 712 | 713 | 714 | @install 715 | class MetadataPathFinder(NullFinder, DistributionFinder): 716 | """A degenerate finder for distribution packages on the file system. 717 | 718 | This finder supplies only a find_distributions() method for versions 719 | of Python that do not have a PathFinder find_distributions(). 720 | """ 721 | 722 | def find_distributions(self, context=DistributionFinder.Context()): 723 | """ 724 | Find distributions. 725 | 726 | Return an iterable of all Distribution instances capable of 727 | loading the metadata for packages matching ``context.name`` 728 | (or all names if ``None`` indicated) along the paths in the list 729 | of directories ``context.path``. 730 | """ 731 | found = self._search_paths(context.name, context.path) 732 | return map(PathDistribution, found) 733 | 734 | @classmethod 735 | def _search_paths(cls, name, paths): 736 | """Find metadata directories in paths heuristically.""" 737 | prepared = Prepared(name) 738 | return itertools.chain.from_iterable( 739 | path.search(prepared) for path in map(FastPath, paths) 740 | ) 741 | 742 | def invalidate_caches(cls): 743 | FastPath.__new__.cache_clear() 744 | 745 | 746 | class PathDistribution(Distribution): 747 | def __init__(self, path: SimplePath): 748 | """Construct a distribution. 749 | 750 | :param path: SimplePath indicating the metadata directory. 751 | """ 752 | self._path = path 753 | 754 | def read_text(self, filename): 755 | with suppress( 756 | FileNotFoundError, 757 | IsADirectoryError, 758 | KeyError, 759 | NotADirectoryError, 760 | PermissionError, 761 | ): 762 | return self._path.joinpath(filename).read_text(encoding='utf-8') 763 | 764 | read_text.__doc__ = Distribution.read_text.__doc__ 765 | 766 | def locate_file(self, path): 767 | return self._path.parent / path 768 | 769 | @property 770 | def _normalized_name(self): 771 | """ 772 | Performance optimization: where possible, resolve the 773 | normalized name from the file system path. 774 | """ 775 | stem = os.path.basename(str(self._path)) 776 | return ( 777 | pass_none(Prepared.normalize)(self._name_from_stem(stem)) 778 | or super()._normalized_name 779 | ) 780 | 781 | @staticmethod 782 | def _name_from_stem(stem): 783 | """ 784 | >>> PathDistribution._name_from_stem('foo-3.0.egg-info') 785 | 'foo' 786 | >>> PathDistribution._name_from_stem('CherryPy-3.0.dist-info') 787 | 'CherryPy' 788 | >>> PathDistribution._name_from_stem('face.egg-info') 789 | 'face' 790 | >>> PathDistribution._name_from_stem('foo.bar') 791 | """ 792 | filename, ext = os.path.splitext(stem) 793 | if ext not in ('.dist-info', '.egg-info'): 794 | return 795 | name, sep, rest = filename.partition('-') 796 | return name 797 | 798 | 799 | def distribution(distribution_name): 800 | """Get the ``Distribution`` instance for the named package. 801 | 802 | :param distribution_name: The name of the distribution package as a string. 803 | :return: A ``Distribution`` instance (or subclass thereof). 804 | """ 805 | return Distribution.from_name(distribution_name) 806 | 807 | 808 | def distributions(**kwargs): 809 | """Get all ``Distribution`` instances in the current environment. 810 | 811 | :return: An iterable of ``Distribution`` instances. 812 | """ 813 | return Distribution.discover(**kwargs) 814 | 815 | 816 | def metadata(distribution_name) -> _meta.PackageMetadata: 817 | """Get the metadata for the named package. 818 | 819 | :param distribution_name: The name of the distribution package to query. 820 | :return: A PackageMetadata containing the parsed metadata. 821 | """ 822 | return Distribution.from_name(distribution_name).metadata 823 | 824 | 825 | def version(distribution_name): 826 | """Get the version string for the named package. 827 | 828 | :param distribution_name: The name of the distribution package to query. 829 | :return: The version string for the package as defined in the package's 830 | "Version" metadata key. 831 | """ 832 | return distribution(distribution_name).version 833 | 834 | 835 | _unique = functools.partial( 836 | unique_everseen, 837 | key=_py39compat.normalized_name, 838 | ) 839 | """ 840 | Wrapper for ``distributions`` to return unique distributions by name. 841 | """ 842 | 843 | 844 | def entry_points(**params) -> EntryPoints: 845 | """Return EntryPoint objects for all installed packages. 846 | 847 | Pass selection parameters (group or name) to filter the 848 | result to entry points matching those properties (see 849 | EntryPoints.select()). 850 | 851 | :return: EntryPoints for all installed packages. 852 | """ 853 | eps = itertools.chain.from_iterable( 854 | dist.entry_points for dist in _unique(distributions()) 855 | ) 856 | return EntryPoints(eps).select(**params) 857 | 858 | 859 | def files(distribution_name): 860 | """Return a list of files for the named package. 861 | 862 | :param distribution_name: The name of the distribution package to query. 863 | :return: List of files composing the distribution. 864 | """ 865 | return distribution(distribution_name).files 866 | 867 | 868 | def requires(distribution_name): 869 | """ 870 | Return a list of requirements for the named package. 871 | 872 | :return: An iterator of requirements, suitable for 873 | packaging.requirement.Requirement. 874 | """ 875 | return distribution(distribution_name).requires 876 | 877 | 878 | def packages_distributions() -> Mapping[str, List[str]]: 879 | """ 880 | Return a mapping of top-level packages to their 881 | distributions. 882 | 883 | >>> import collections.abc 884 | >>> pkgs = packages_distributions() 885 | >>> all(isinstance(dist, collections.abc.Sequence) for dist in pkgs.values()) 886 | True 887 | """ 888 | pkg_to_dist = collections.defaultdict(list) 889 | for dist in distributions(): 890 | for pkg in _top_level_declared(dist) or _top_level_inferred(dist): 891 | pkg_to_dist[pkg].append(dist.metadata['Name']) 892 | return dict(pkg_to_dist) 893 | 894 | 895 | def _top_level_declared(dist): 896 | return (dist.read_text('top_level.txt') or '').split() 897 | 898 | 899 | def _top_level_inferred(dist): 900 | return { 901 | f.parts[0] if len(f.parts) > 1 else f.with_suffix('').name 902 | for f in always_iterable(dist.files) 903 | if f.suffix == ".py" 904 | } 905 | -------------------------------------------------------------------------------- /vendor/importlib_metadata/importlib_metadata/_adapters.py: -------------------------------------------------------------------------------- 1 | import re 2 | import textwrap 3 | import email.message 4 | 5 | from ._text import FoldedCase 6 | 7 | 8 | class Message(email.message.Message): 9 | multiple_use_keys = set( 10 | map( 11 | FoldedCase, 12 | [ 13 | 'Classifier', 14 | 'Obsoletes-Dist', 15 | 'Platform', 16 | 'Project-URL', 17 | 'Provides-Dist', 18 | 'Provides-Extra', 19 | 'Requires-Dist', 20 | 'Requires-External', 21 | 'Supported-Platform', 22 | 'Dynamic', 23 | ], 24 | ) 25 | ) 26 | """ 27 | Keys that may be indicated multiple times per PEP 566. 28 | """ 29 | 30 | def __new__(cls, orig: email.message.Message): 31 | res = super().__new__(cls) 32 | vars(res).update(vars(orig)) 33 | return res 34 | 35 | def __init__(self, *args, **kwargs): 36 | self._headers = self._repair_headers() 37 | 38 | # suppress spurious error from mypy 39 | def __iter__(self): 40 | return super().__iter__() 41 | 42 | def _repair_headers(self): 43 | def redent(value): 44 | "Correct for RFC822 indentation" 45 | if not value or '\n' not in value: 46 | return value 47 | return textwrap.dedent(' ' * 8 + value) 48 | 49 | headers = [(key, redent(value)) for key, value in vars(self)['_headers']] 50 | if self._payload: 51 | headers.append(('Description', self.get_payload())) 52 | return headers 53 | 54 | @property 55 | def json(self): 56 | """ 57 | Convert PackageMetadata to a JSON-compatible format 58 | per PEP 0566. 59 | """ 60 | 61 | def transform(key): 62 | value = self.get_all(key) if key in self.multiple_use_keys else self[key] 63 | if key == 'Keywords': 64 | value = re.split(r'\s+', value) 65 | tk = key.lower().replace('-', '_') 66 | return tk, value 67 | 68 | return dict(map(transform, map(FoldedCase, self))) 69 | -------------------------------------------------------------------------------- /vendor/importlib_metadata/importlib_metadata/_collections.py: -------------------------------------------------------------------------------- 1 | import collections 2 | 3 | 4 | # from jaraco.collections 3.3 5 | class FreezableDefaultDict(collections.defaultdict): 6 | """ 7 | Often it is desirable to prevent the mutation of 8 | a default dict after its initial construction, such 9 | as to prevent mutation during iteration. 10 | 11 | >>> dd = FreezableDefaultDict(list) 12 | >>> dd[0].append('1') 13 | >>> dd.freeze() 14 | >>> dd[1] 15 | [] 16 | >>> len(dd) 17 | 1 18 | """ 19 | 20 | def __missing__(self, key): 21 | return getattr(self, '_frozen', super().__missing__)(key) 22 | 23 | def freeze(self): 24 | self._frozen = lambda key: self.default_factory() 25 | 26 | 27 | class Pair(collections.namedtuple('Pair', 'name value')): 28 | @classmethod 29 | def parse(cls, text): 30 | return cls(*map(str.strip, text.split("=", 1))) 31 | -------------------------------------------------------------------------------- /vendor/importlib_metadata/importlib_metadata/_compat.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import platform 3 | 4 | 5 | __all__ = ['install', 'NullFinder', 'Protocol'] 6 | 7 | 8 | try: 9 | from typing import Protocol 10 | except ImportError: # pragma: no cover 11 | # Python 3.7 compatibility 12 | from typing_extensions import Protocol # type: ignore 13 | 14 | 15 | def install(cls): 16 | """ 17 | Class decorator for installation on sys.meta_path. 18 | 19 | Adds the backport DistributionFinder to sys.meta_path and 20 | attempts to disable the finder functionality of the stdlib 21 | DistributionFinder. 22 | """ 23 | sys.meta_path.append(cls()) 24 | disable_stdlib_finder() 25 | return cls 26 | 27 | 28 | def disable_stdlib_finder(): 29 | """ 30 | Give the backport primacy for discovering path-based distributions 31 | by monkey-patching the stdlib O_O. 32 | 33 | See #91 for more background for rationale on this sketchy 34 | behavior. 35 | """ 36 | 37 | def matches(finder): 38 | return getattr( 39 | finder, '__module__', None 40 | ) == '_frozen_importlib_external' and hasattr(finder, 'find_distributions') 41 | 42 | for finder in filter(matches, sys.meta_path): # pragma: nocover 43 | del finder.find_distributions 44 | 45 | 46 | class NullFinder: 47 | """ 48 | A "Finder" (aka "MetaClassFinder") that never finds any modules, 49 | but may find distributions. 50 | """ 51 | 52 | @staticmethod 53 | def find_spec(*args, **kwargs): 54 | return None 55 | 56 | # In Python 2, the import system requires finders 57 | # to have a find_module() method, but this usage 58 | # is deprecated in Python 3 in favor of find_spec(). 59 | # For the purposes of this finder (i.e. being present 60 | # on sys.meta_path but having no other import 61 | # system functionality), the two methods are identical. 62 | find_module = find_spec 63 | 64 | 65 | def pypy_partial(val): 66 | """ 67 | Adjust for variable stacklevel on partial under PyPy. 68 | 69 | Workaround for #327. 70 | """ 71 | is_pypy = platform.python_implementation() == 'PyPy' 72 | return val + is_pypy 73 | -------------------------------------------------------------------------------- /vendor/importlib_metadata/importlib_metadata/_functools.py: -------------------------------------------------------------------------------- 1 | import types 2 | import functools 3 | 4 | 5 | # from jaraco.functools 3.3 6 | def method_cache(method, cache_wrapper=None): 7 | """ 8 | Wrap lru_cache to support storing the cache data in the object instances. 9 | 10 | Abstracts the common paradigm where the method explicitly saves an 11 | underscore-prefixed protected property on first call and returns that 12 | subsequently. 13 | 14 | >>> class MyClass: 15 | ... calls = 0 16 | ... 17 | ... @method_cache 18 | ... def method(self, value): 19 | ... self.calls += 1 20 | ... return value 21 | 22 | >>> a = MyClass() 23 | >>> a.method(3) 24 | 3 25 | >>> for x in range(75): 26 | ... res = a.method(x) 27 | >>> a.calls 28 | 75 29 | 30 | Note that the apparent behavior will be exactly like that of lru_cache 31 | except that the cache is stored on each instance, so values in one 32 | instance will not flush values from another, and when an instance is 33 | deleted, so are the cached values for that instance. 34 | 35 | >>> b = MyClass() 36 | >>> for x in range(35): 37 | ... res = b.method(x) 38 | >>> b.calls 39 | 35 40 | >>> a.method(0) 41 | 0 42 | >>> a.calls 43 | 75 44 | 45 | Note that if method had been decorated with ``functools.lru_cache()``, 46 | a.calls would have been 76 (due to the cached value of 0 having been 47 | flushed by the 'b' instance). 48 | 49 | Clear the cache with ``.cache_clear()`` 50 | 51 | >>> a.method.cache_clear() 52 | 53 | Same for a method that hasn't yet been called. 54 | 55 | >>> c = MyClass() 56 | >>> c.method.cache_clear() 57 | 58 | Another cache wrapper may be supplied: 59 | 60 | >>> cache = functools.lru_cache(maxsize=2) 61 | >>> MyClass.method2 = method_cache(lambda self: 3, cache_wrapper=cache) 62 | >>> a = MyClass() 63 | >>> a.method2() 64 | 3 65 | 66 | Caution - do not subsequently wrap the method with another decorator, such 67 | as ``@property``, which changes the semantics of the function. 68 | 69 | See also 70 | http://code.activestate.com/recipes/577452-a-memoize-decorator-for-instance-methods/ 71 | for another implementation and additional justification. 72 | """ 73 | cache_wrapper = cache_wrapper or functools.lru_cache() 74 | 75 | def wrapper(self, *args, **kwargs): 76 | # it's the first call, replace the method with a cached, bound method 77 | bound_method = types.MethodType(method, self) 78 | cached_method = cache_wrapper(bound_method) 79 | setattr(self, method.__name__, cached_method) 80 | return cached_method(*args, **kwargs) 81 | 82 | # Support cache clear even before cache has been created. 83 | wrapper.cache_clear = lambda: None 84 | 85 | return wrapper 86 | 87 | 88 | # From jaraco.functools 3.3 89 | def pass_none(func): 90 | """ 91 | Wrap func so it's not called if its first param is None 92 | 93 | >>> print_text = pass_none(print) 94 | >>> print_text('text') 95 | text 96 | >>> print_text(None) 97 | """ 98 | 99 | @functools.wraps(func) 100 | def wrapper(param, *args, **kwargs): 101 | if param is not None: 102 | return func(param, *args, **kwargs) 103 | 104 | return wrapper 105 | -------------------------------------------------------------------------------- /vendor/importlib_metadata/importlib_metadata/_itertools.py: -------------------------------------------------------------------------------- 1 | from itertools import filterfalse 2 | 3 | 4 | def unique_everseen(iterable, key=None): 5 | "List unique elements, preserving order. Remember all elements ever seen." 6 | # unique_everseen('AAAABBBCCDAABBB') --> A B C D 7 | # unique_everseen('ABBCcAD', str.lower) --> A B C D 8 | seen = set() 9 | seen_add = seen.add 10 | if key is None: 11 | for element in filterfalse(seen.__contains__, iterable): 12 | seen_add(element) 13 | yield element 14 | else: 15 | for element in iterable: 16 | k = key(element) 17 | if k not in seen: 18 | seen_add(k) 19 | yield element 20 | 21 | 22 | # copied from more_itertools 8.8 23 | def always_iterable(obj, base_type=(str, bytes)): 24 | """If *obj* is iterable, return an iterator over its items:: 25 | 26 | >>> obj = (1, 2, 3) 27 | >>> list(always_iterable(obj)) 28 | [1, 2, 3] 29 | 30 | If *obj* is not iterable, return a one-item iterable containing *obj*:: 31 | 32 | >>> obj = 1 33 | >>> list(always_iterable(obj)) 34 | [1] 35 | 36 | If *obj* is ``None``, return an empty iterable: 37 | 38 | >>> obj = None 39 | >>> list(always_iterable(None)) 40 | [] 41 | 42 | By default, binary and text strings are not considered iterable:: 43 | 44 | >>> obj = 'foo' 45 | >>> list(always_iterable(obj)) 46 | ['foo'] 47 | 48 | If *base_type* is set, objects for which ``isinstance(obj, base_type)`` 49 | returns ``True`` won't be considered iterable. 50 | 51 | >>> obj = {'a': 1} 52 | >>> list(always_iterable(obj)) # Iterate over the dict's keys 53 | ['a'] 54 | >>> list(always_iterable(obj, base_type=dict)) # Treat dicts as a unit 55 | [{'a': 1}] 56 | 57 | Set *base_type* to ``None`` to avoid any special handling and treat objects 58 | Python considers iterable as iterable: 59 | 60 | >>> obj = 'foo' 61 | >>> list(always_iterable(obj, base_type=None)) 62 | ['f', 'o', 'o'] 63 | """ 64 | if obj is None: 65 | return iter(()) 66 | 67 | if (base_type is not None) and isinstance(obj, base_type): 68 | return iter((obj,)) 69 | 70 | try: 71 | return iter(obj) 72 | except TypeError: 73 | return iter((obj,)) 74 | -------------------------------------------------------------------------------- /vendor/importlib_metadata/importlib_metadata/_meta.py: -------------------------------------------------------------------------------- 1 | from ._compat import Protocol 2 | from typing import Any, Dict, Iterator, List, TypeVar, Union 3 | 4 | 5 | _T = TypeVar("_T") 6 | 7 | 8 | class PackageMetadata(Protocol): 9 | def __len__(self) -> int: 10 | ... # pragma: no cover 11 | 12 | def __contains__(self, item: str) -> bool: 13 | ... # pragma: no cover 14 | 15 | def __getitem__(self, key: str) -> str: 16 | ... # pragma: no cover 17 | 18 | def __iter__(self) -> Iterator[str]: 19 | ... # pragma: no cover 20 | 21 | def get_all(self, name: str, failobj: _T = ...) -> Union[List[Any], _T]: 22 | """ 23 | Return all values associated with a possibly multi-valued key. 24 | """ 25 | 26 | @property 27 | def json(self) -> Dict[str, Union[str, List[str]]]: 28 | """ 29 | A JSON-compatible form of the metadata. 30 | """ 31 | 32 | 33 | class SimplePath(Protocol): 34 | """ 35 | A minimal subset of pathlib.Path required by PathDistribution. 36 | """ 37 | 38 | def joinpath(self) -> 'SimplePath': 39 | ... # pragma: no cover 40 | 41 | def __truediv__(self) -> 'SimplePath': 42 | ... # pragma: no cover 43 | 44 | def parent(self) -> 'SimplePath': 45 | ... # pragma: no cover 46 | 47 | def read_text(self) -> str: 48 | ... # pragma: no cover 49 | -------------------------------------------------------------------------------- /vendor/importlib_metadata/importlib_metadata/_py39compat.py: -------------------------------------------------------------------------------- 1 | """ 2 | Compatibility layer with Python 3.8/3.9 3 | """ 4 | from typing import TYPE_CHECKING, Any, Optional, Tuple 5 | 6 | if TYPE_CHECKING: # pragma: no cover 7 | # Prevent circular imports on runtime. 8 | from . import Distribution, EntryPoint 9 | else: 10 | Distribution = EntryPoint = Any 11 | 12 | 13 | def normalized_name(dist: Distribution) -> Optional[str]: 14 | """ 15 | Honor name normalization for distributions that don't provide ``_normalized_name``. 16 | """ 17 | try: 18 | return dist._normalized_name 19 | except AttributeError: 20 | from . import Prepared # -> delay to prevent circular imports. 21 | 22 | return Prepared.normalize(getattr(dist, "name", None) or dist.metadata['Name']) 23 | 24 | 25 | def ep_matches(ep: EntryPoint, **params) -> Tuple[EntryPoint, bool]: 26 | """ 27 | Workaround for ``EntryPoint`` objects without the ``matches`` method. 28 | For the sake of convenience, a tuple is returned containing not only the 29 | boolean value corresponding to the predicate evalutation, but also a compatible 30 | ``EntryPoint`` object that can be safely used at a later stage. 31 | 32 | For example, the following sequences of expressions should be compatible: 33 | 34 | # Sequence 1: using the compatibility layer 35 | candidates = (_py39compat.ep_matches(ep, **params) for ep in entry_points) 36 | [ep for ep, predicate in candidates if predicate] 37 | 38 | # Sequence 2: using Python 3.9+ 39 | [ep for ep in entry_points if ep.matches(**params)] 40 | """ 41 | try: 42 | return ep, ep.matches(**params) 43 | except AttributeError: 44 | from . import EntryPoint # -> delay to prevent circular imports. 45 | 46 | # Reconstruct the EntryPoint object to make sure it is compatible. 47 | _ep = EntryPoint(ep.name, ep.value, ep.group) 48 | return _ep, _ep.matches(**params) 49 | -------------------------------------------------------------------------------- /vendor/importlib_metadata/importlib_metadata/_text.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | from ._functools import method_cache 4 | 5 | 6 | # from jaraco.text 3.5 7 | class FoldedCase(str): 8 | """ 9 | A case insensitive string class; behaves just like str 10 | except compares equal when the only variation is case. 11 | 12 | >>> s = FoldedCase('hello world') 13 | 14 | >>> s == 'Hello World' 15 | True 16 | 17 | >>> 'Hello World' == s 18 | True 19 | 20 | >>> s != 'Hello World' 21 | False 22 | 23 | >>> s.index('O') 24 | 4 25 | 26 | >>> s.split('O') 27 | ['hell', ' w', 'rld'] 28 | 29 | >>> sorted(map(FoldedCase, ['GAMMA', 'alpha', 'Beta'])) 30 | ['alpha', 'Beta', 'GAMMA'] 31 | 32 | Sequence membership is straightforward. 33 | 34 | >>> "Hello World" in [s] 35 | True 36 | >>> s in ["Hello World"] 37 | True 38 | 39 | You may test for set inclusion, but candidate and elements 40 | must both be folded. 41 | 42 | >>> FoldedCase("Hello World") in {s} 43 | True 44 | >>> s in {FoldedCase("Hello World")} 45 | True 46 | 47 | String inclusion works as long as the FoldedCase object 48 | is on the right. 49 | 50 | >>> "hello" in FoldedCase("Hello World") 51 | True 52 | 53 | But not if the FoldedCase object is on the left: 54 | 55 | >>> FoldedCase('hello') in 'Hello World' 56 | False 57 | 58 | In that case, use in_: 59 | 60 | >>> FoldedCase('hello').in_('Hello World') 61 | True 62 | 63 | >>> FoldedCase('hello') > FoldedCase('Hello') 64 | False 65 | """ 66 | 67 | def __lt__(self, other): 68 | return self.lower() < other.lower() 69 | 70 | def __gt__(self, other): 71 | return self.lower() > other.lower() 72 | 73 | def __eq__(self, other): 74 | return self.lower() == other.lower() 75 | 76 | def __ne__(self, other): 77 | return self.lower() != other.lower() 78 | 79 | def __hash__(self): 80 | return hash(self.lower()) 81 | 82 | def __contains__(self, other): 83 | return super().lower().__contains__(other.lower()) 84 | 85 | def in_(self, other): 86 | "Does self appear in other?" 87 | return self in FoldedCase(other) 88 | 89 | # cache lower since it's likely to be called frequently. 90 | @method_cache 91 | def lower(self): 92 | return super().lower() 93 | 94 | def index(self, sub): 95 | return self.lower().index(sub.lower()) 96 | 97 | def split(self, splitter=' ', maxsplit=0): 98 | pattern = re.compile(re.escape(splitter), re.I) 99 | return pattern.split(self, maxsplit) 100 | -------------------------------------------------------------------------------- /vendor/importlib_metadata/importlib_metadata/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cedarai/rules_pyvenv/7b3e110e361ba13c972dd74eb17420a81da36f1f/vendor/importlib_metadata/importlib_metadata/py.typed -------------------------------------------------------------------------------- /vendor/zipp/BUILD.bazel: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//visibility:public"]) 2 | 3 | py_library( 4 | name = "zipp", 5 | srcs = glob(["**/*.py"]), 6 | imports = ["."], 7 | ) 8 | -------------------------------------------------------------------------------- /vendor/zipp/CHANGES.rst: -------------------------------------------------------------------------------- 1 | v3.9.0 2 | ====== 3 | 4 | * #81: ``Path`` objects are now pickleable if they've been 5 | constructed from pickleable objects. Any restored objects 6 | will re-construct the zip file with the original arguments. 7 | 8 | v3.8.1 9 | ====== 10 | 11 | Refreshed packaging. 12 | 13 | Enrolled with Tidelift. 14 | 15 | v3.8.0 16 | ====== 17 | 18 | Removed compatibility code. 19 | 20 | v3.7.0 21 | ====== 22 | 23 | Require Python 3.7 or later. 24 | 25 | v3.6.0 26 | ====== 27 | 28 | #78: Only ``Path`` is exposed in the public API. 29 | 30 | v3.5.1 31 | ====== 32 | 33 | #77: Remove news file intended only for CPython. 34 | 35 | v3.5.0 36 | ====== 37 | 38 | #74 and bpo-44095: Added ``.suffix``, ``.suffixes``, 39 | and ``.stem`` properties. 40 | 41 | v3.4.2 42 | ====== 43 | 44 | Refresh package metadata. 45 | 46 | v3.4.1 47 | ====== 48 | 49 | Refresh packaging. 50 | 51 | v3.4.0 52 | ====== 53 | 54 | #68 and bpo-42090: ``Path.joinpath`` now takes arbitrary 55 | positional arguments and no longer accepts ``add`` as a 56 | keyword argument. 57 | 58 | v3.3.2 59 | ====== 60 | 61 | Updated project metadata including badges. 62 | 63 | v3.3.1 64 | ====== 65 | 66 | bpo-42043: Add tests capturing subclassing requirements. 67 | 68 | v3.3.0 69 | ====== 70 | 71 | #9: ``Path`` objects now expose a ``.filename`` attribute 72 | and rely on that to resolve ``.name`` and ``.parent`` when 73 | the ``Path`` object is at the root of the zipfile. 74 | 75 | v3.2.0 76 | ====== 77 | 78 | #57 and bpo-40564: Mutate the passed ZipFile object 79 | type instead of making a copy. Prevents issues when 80 | both the local copy and the caller's copy attempt to 81 | close the same file handle. 82 | 83 | #56 and bpo-41035: ``Path._next`` now honors 84 | subclasses. 85 | 86 | #55: ``Path.is_file()`` now returns False for non-existent names. 87 | 88 | v3.1.0 89 | ====== 90 | 91 | #47: ``.open`` now raises ``FileNotFoundError`` and 92 | ``IsADirectoryError`` when appropriate. 93 | 94 | v3.0.0 95 | ====== 96 | 97 | #44: Merge with v1.2.0. 98 | 99 | v1.2.0 100 | ====== 101 | 102 | #44: ``zipp.Path.open()`` now supports a compatible signature 103 | as ``pathlib.Path.open()``, accepting text (default) or binary 104 | modes and soliciting keyword parameters passed through to 105 | ``io.TextIOWrapper`` (encoding, newline, etc). The stream is 106 | opened in text-mode by default now. ``open`` no 107 | longer accepts ``pwd`` as a positional argument and does not 108 | accept the ``force_zip64`` parameter at all. This change is 109 | a backward-incompatible change for that single function. 110 | 111 | v2.2.1 112 | ====== 113 | 114 | #43: Merge with v1.1.1. 115 | 116 | v1.1.1 117 | ====== 118 | 119 | #43: Restored performance of implicit dir computation. 120 | 121 | v2.2.0 122 | ====== 123 | 124 | #36: Rebuild package with minimum Python version declared both 125 | in package metadata and in the python tag. 126 | 127 | v2.1.0 128 | ====== 129 | 130 | #32: Merge with v1.1.0. 131 | 132 | v1.1.0 133 | ====== 134 | 135 | #32: For read-only zip files, complexity of ``.exists`` and 136 | ``joinpath`` is now constant time instead of ``O(n)``, preventing 137 | quadratic time in common use-cases and rendering large 138 | zip files unusable for Path. Big thanks to Benjy Weinberger 139 | for the bug report and contributed fix (#33). 140 | 141 | v2.0.1 142 | ====== 143 | 144 | #30: Corrected version inference (from jaraco/skeleton#12). 145 | 146 | v2.0.0 147 | ====== 148 | 149 | Require Python 3.6 or later. 150 | 151 | v1.0.0 152 | ====== 153 | 154 | Re-release of 0.6 to correspond with release as found in 155 | Python 3.8. 156 | 157 | v0.6.0 158 | ====== 159 | 160 | #12: When adding implicit dirs, ensure that ancestral directories 161 | are added and that duplicates are excluded. 162 | 163 | The library now relies on 164 | `more_itertools `_. 165 | 166 | v0.5.2 167 | ====== 168 | 169 | #7: Parent of a directory now actually returns the parent. 170 | 171 | v0.5.1 172 | ====== 173 | 174 | Declared package as backport. 175 | 176 | v0.5.0 177 | ====== 178 | 179 | Add ``.joinpath()`` method and ``.parent`` property. 180 | 181 | Now a backport release of the ``zipfile.Path`` class. 182 | 183 | v0.4.0 184 | ====== 185 | 186 | #4: Add support for zip files with implied directories. 187 | 188 | v0.3.3 189 | ====== 190 | 191 | #3: Fix issue where ``.name`` on a directory was empty. 192 | 193 | v0.3.2 194 | ====== 195 | 196 | #2: Fix TypeError on Python 2.7 when classic division is used. 197 | 198 | v0.3.1 199 | ====== 200 | 201 | #1: Fix TypeError on Python 3.5 when joining to a path-like object. 202 | 203 | v0.3.0 204 | ====== 205 | 206 | Add support for constructing a ``zipp.Path`` from any path-like 207 | object. 208 | 209 | ``zipp.Path`` is now a new-style class on Python 2.7. 210 | 211 | v0.2.1 212 | ====== 213 | 214 | Fix issue with ``__str__``. 215 | 216 | v0.2.0 217 | ====== 218 | 219 | Drop reliance on future-fstrings. 220 | 221 | v0.1.0 222 | ====== 223 | 224 | Initial release with basic functionality. 225 | -------------------------------------------------------------------------------- /vendor/zipp/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright Jason R. Coombs 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to 5 | deal in the Software without restriction, including without limitation the 6 | rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 7 | sell copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 19 | IN THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /vendor/zipp/README.rst: -------------------------------------------------------------------------------- 1 | .. image:: https://img.shields.io/pypi/v/zipp.svg 2 | :target: `PyPI link`_ 3 | 4 | .. image:: https://img.shields.io/pypi/pyversions/zipp.svg 5 | :target: `PyPI link`_ 6 | 7 | .. _PyPI link: https://pypi.org/project/zipp 8 | 9 | .. image:: https://github.com/jaraco/zipp/workflows/tests/badge.svg 10 | :target: https://github.com/jaraco/zipp/actions?query=workflow%3A%22tests%22 11 | :alt: tests 12 | 13 | .. image:: https://img.shields.io/badge/code%20style-black-000000.svg 14 | :target: https://github.com/psf/black 15 | :alt: Code style: Black 16 | 17 | .. .. image:: https://readthedocs.org/projects/skeleton/badge/?version=latest 18 | .. :target: https://skeleton.readthedocs.io/en/latest/?badge=latest 19 | 20 | .. image:: https://img.shields.io/badge/skeleton-2022-informational 21 | :target: https://blog.jaraco.com/skeleton 22 | 23 | .. image:: https://tidelift.com/badges/package/pypi/zipp 24 | :target: https://tidelift.com/subscription/pkg/pypi-zipp?utm_source=pypi-zipp&utm_medium=readme 25 | 26 | 27 | A pathlib-compatible Zipfile object wrapper. Official backport of the standard library 28 | `Path object `_. 29 | 30 | 31 | Compatibility 32 | ============= 33 | 34 | New features are introduced in this third-party library and later merged 35 | into CPython. The following table indicates which versions of this library 36 | were contributed to different versions in the standard library: 37 | 38 | .. list-table:: 39 | :header-rows: 1 40 | 41 | * - zipp 42 | - stdlib 43 | * - 3.5 44 | - 3.11 45 | * - 3.3 46 | - 3.9 47 | * - 1.0 48 | - 3.8 49 | 50 | 51 | Usage 52 | ===== 53 | 54 | Use ``zipp.Path`` in place of ``zipfile.Path`` on any Python. 55 | 56 | For Enterprise 57 | ============== 58 | 59 | Available as part of the Tidelift Subscription. 60 | 61 | This project and the maintainers of thousands of other packages are working with Tidelift to deliver one enterprise subscription that covers all of the open source you use. 62 | 63 | `Learn more `_. 64 | 65 | Security Contact 66 | ================ 67 | 68 | To report a security vulnerability, please use the 69 | `Tidelift security contact `_. 70 | Tidelift will coordinate the fix and disclosure. 71 | -------------------------------------------------------------------------------- /vendor/zipp/zipp.py: -------------------------------------------------------------------------------- 1 | import io 2 | import posixpath 3 | import zipfile 4 | import itertools 5 | import contextlib 6 | import pathlib 7 | 8 | 9 | __all__ = ['Path'] 10 | 11 | 12 | def _parents(path): 13 | """ 14 | Given a path with elements separated by 15 | posixpath.sep, generate all parents of that path. 16 | 17 | >>> list(_parents('b/d')) 18 | ['b'] 19 | >>> list(_parents('/b/d/')) 20 | ['/b'] 21 | >>> list(_parents('b/d/f/')) 22 | ['b/d', 'b'] 23 | >>> list(_parents('b')) 24 | [] 25 | >>> list(_parents('')) 26 | [] 27 | """ 28 | return itertools.islice(_ancestry(path), 1, None) 29 | 30 | 31 | def _ancestry(path): 32 | """ 33 | Given a path with elements separated by 34 | posixpath.sep, generate all elements of that path 35 | 36 | >>> list(_ancestry('b/d')) 37 | ['b/d', 'b'] 38 | >>> list(_ancestry('/b/d/')) 39 | ['/b/d', '/b'] 40 | >>> list(_ancestry('b/d/f/')) 41 | ['b/d/f', 'b/d', 'b'] 42 | >>> list(_ancestry('b')) 43 | ['b'] 44 | >>> list(_ancestry('')) 45 | [] 46 | """ 47 | path = path.rstrip(posixpath.sep) 48 | while path and path != posixpath.sep: 49 | yield path 50 | path, tail = posixpath.split(path) 51 | 52 | 53 | _dedupe = dict.fromkeys 54 | """Deduplicate an iterable in original order""" 55 | 56 | 57 | def _difference(minuend, subtrahend): 58 | """ 59 | Return items in minuend not in subtrahend, retaining order 60 | with O(1) lookup. 61 | """ 62 | return itertools.filterfalse(set(subtrahend).__contains__, minuend) 63 | 64 | 65 | class InitializedState: 66 | """ 67 | Mix-in to save the initialization state for pickling. 68 | """ 69 | 70 | def __init__(self, *args, **kwargs): 71 | self.__args = args 72 | self.__kwargs = kwargs 73 | super().__init__(*args, **kwargs) 74 | 75 | def __getstate__(self): 76 | return self.__args, self.__kwargs 77 | 78 | def __setstate__(self, state): 79 | args, kwargs = state 80 | super().__init__(*args, **kwargs) 81 | 82 | 83 | class CompleteDirs(InitializedState, zipfile.ZipFile): 84 | """ 85 | A ZipFile subclass that ensures that implied directories 86 | are always included in the namelist. 87 | """ 88 | 89 | @staticmethod 90 | def _implied_dirs(names): 91 | parents = itertools.chain.from_iterable(map(_parents, names)) 92 | as_dirs = (p + posixpath.sep for p in parents) 93 | return _dedupe(_difference(as_dirs, names)) 94 | 95 | def namelist(self): 96 | names = super(CompleteDirs, self).namelist() 97 | return names + list(self._implied_dirs(names)) 98 | 99 | def _name_set(self): 100 | return set(self.namelist()) 101 | 102 | def resolve_dir(self, name): 103 | """ 104 | If the name represents a directory, return that name 105 | as a directory (with the trailing slash). 106 | """ 107 | names = self._name_set() 108 | dirname = name + '/' 109 | dir_match = name not in names and dirname in names 110 | return dirname if dir_match else name 111 | 112 | @classmethod 113 | def make(cls, source): 114 | """ 115 | Given a source (filename or zipfile), return an 116 | appropriate CompleteDirs subclass. 117 | """ 118 | if isinstance(source, CompleteDirs): 119 | return source 120 | 121 | if not isinstance(source, zipfile.ZipFile): 122 | return cls(source) 123 | 124 | # Only allow for FastLookup when supplied zipfile is read-only 125 | if 'r' not in source.mode: 126 | cls = CompleteDirs 127 | 128 | source.__class__ = cls 129 | return source 130 | 131 | 132 | class FastLookup(CompleteDirs): 133 | """ 134 | ZipFile subclass to ensure implicit 135 | dirs exist and are resolved rapidly. 136 | """ 137 | 138 | def namelist(self): 139 | with contextlib.suppress(AttributeError): 140 | return self.__names 141 | self.__names = super(FastLookup, self).namelist() 142 | return self.__names 143 | 144 | def _name_set(self): 145 | with contextlib.suppress(AttributeError): 146 | return self.__lookup 147 | self.__lookup = super(FastLookup, self)._name_set() 148 | return self.__lookup 149 | 150 | 151 | class Path: 152 | """ 153 | A pathlib-compatible interface for zip files. 154 | 155 | Consider a zip file with this structure:: 156 | 157 | . 158 | ├── a.txt 159 | └── b 160 | ├── c.txt 161 | └── d 162 | └── e.txt 163 | 164 | >>> data = io.BytesIO() 165 | >>> zf = zipfile.ZipFile(data, 'w') 166 | >>> zf.writestr('a.txt', 'content of a') 167 | >>> zf.writestr('b/c.txt', 'content of c') 168 | >>> zf.writestr('b/d/e.txt', 'content of e') 169 | >>> zf.filename = 'mem/abcde.zip' 170 | 171 | Path accepts the zipfile object itself or a filename 172 | 173 | >>> root = Path(zf) 174 | 175 | From there, several path operations are available. 176 | 177 | Directory iteration (including the zip file itself): 178 | 179 | >>> a, b = root.iterdir() 180 | >>> a 181 | Path('mem/abcde.zip', 'a.txt') 182 | >>> b 183 | Path('mem/abcde.zip', 'b/') 184 | 185 | name property: 186 | 187 | >>> b.name 188 | 'b' 189 | 190 | join with divide operator: 191 | 192 | >>> c = b / 'c.txt' 193 | >>> c 194 | Path('mem/abcde.zip', 'b/c.txt') 195 | >>> c.name 196 | 'c.txt' 197 | 198 | Read text: 199 | 200 | >>> c.read_text() 201 | 'content of c' 202 | 203 | existence: 204 | 205 | >>> c.exists() 206 | True 207 | >>> (b / 'missing.txt').exists() 208 | False 209 | 210 | Coercion to string: 211 | 212 | >>> import os 213 | >>> str(c).replace(os.sep, posixpath.sep) 214 | 'mem/abcde.zip/b/c.txt' 215 | 216 | At the root, ``name``, ``filename``, and ``parent`` 217 | resolve to the zipfile. Note these attributes are not 218 | valid and will raise a ``ValueError`` if the zipfile 219 | has no filename. 220 | 221 | >>> root.name 222 | 'abcde.zip' 223 | >>> str(root.filename).replace(os.sep, posixpath.sep) 224 | 'mem/abcde.zip' 225 | >>> str(root.parent) 226 | 'mem' 227 | """ 228 | 229 | __repr = "{self.__class__.__name__}({self.root.filename!r}, {self.at!r})" 230 | 231 | def __init__(self, root, at=""): 232 | """ 233 | Construct a Path from a ZipFile or filename. 234 | 235 | Note: When the source is an existing ZipFile object, 236 | its type (__class__) will be mutated to a 237 | specialized type. If the caller wishes to retain the 238 | original type, the caller should either create a 239 | separate ZipFile object or pass a filename. 240 | """ 241 | self.root = FastLookup.make(root) 242 | self.at = at 243 | 244 | def open(self, mode='r', *args, pwd=None, **kwargs): 245 | """ 246 | Open this entry as text or binary following the semantics 247 | of ``pathlib.Path.open()`` by passing arguments through 248 | to io.TextIOWrapper(). 249 | """ 250 | if self.is_dir(): 251 | raise IsADirectoryError(self) 252 | zip_mode = mode[0] 253 | if not self.exists() and zip_mode == 'r': 254 | raise FileNotFoundError(self) 255 | stream = self.root.open(self.at, zip_mode, pwd=pwd) 256 | if 'b' in mode: 257 | if args or kwargs: 258 | raise ValueError("encoding args invalid for binary operation") 259 | return stream 260 | return io.TextIOWrapper(stream, *args, **kwargs) 261 | 262 | @property 263 | def name(self): 264 | return pathlib.Path(self.at).name or self.filename.name 265 | 266 | @property 267 | def suffix(self): 268 | return pathlib.Path(self.at).suffix or self.filename.suffix 269 | 270 | @property 271 | def suffixes(self): 272 | return pathlib.Path(self.at).suffixes or self.filename.suffixes 273 | 274 | @property 275 | def stem(self): 276 | return pathlib.Path(self.at).stem or self.filename.stem 277 | 278 | @property 279 | def filename(self): 280 | return pathlib.Path(self.root.filename).joinpath(self.at) 281 | 282 | def read_text(self, *args, **kwargs): 283 | with self.open('r', *args, **kwargs) as strm: 284 | return strm.read() 285 | 286 | def read_bytes(self): 287 | with self.open('rb') as strm: 288 | return strm.read() 289 | 290 | def _is_child(self, path): 291 | return posixpath.dirname(path.at.rstrip("/")) == self.at.rstrip("/") 292 | 293 | def _next(self, at): 294 | return self.__class__(self.root, at) 295 | 296 | def is_dir(self): 297 | return not self.at or self.at.endswith("/") 298 | 299 | def is_file(self): 300 | return self.exists() and not self.is_dir() 301 | 302 | def exists(self): 303 | return self.at in self.root._name_set() 304 | 305 | def iterdir(self): 306 | if not self.is_dir(): 307 | raise ValueError("Can't listdir a file") 308 | subs = map(self._next, self.root.namelist()) 309 | return filter(self._is_child, subs) 310 | 311 | def __str__(self): 312 | return posixpath.join(self.root.filename, self.at) 313 | 314 | def __repr__(self): 315 | return self.__repr.format(self=self) 316 | 317 | def joinpath(self, *other): 318 | next = posixpath.join(self.at, *other) 319 | return self._next(self.root.resolve_dir(next)) 320 | 321 | __truediv__ = joinpath 322 | 323 | @property 324 | def parent(self): 325 | if not self.at: 326 | return self.filename.parent 327 | parent_at = posixpath.dirname(self.at.rstrip('/')) 328 | if parent_at: 329 | parent_at += '/' 330 | return self._next(parent_at) 331 | -------------------------------------------------------------------------------- /venv.bzl: -------------------------------------------------------------------------------- 1 | # Copyright 2021 cedar.ai. All rights reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | load("@rules_python//python:defs.bzl", "py_binary") 16 | load("@rules_python//python:py_info.bzl", "PyInfo") 17 | 18 | PYTHON_TOOLCHAIN_TYPE = "@bazel_tools//tools/python:toolchain_type" 19 | 20 | def _py_venv_deps_impl(ctx): 21 | toolchain_depset = ctx.toolchains[PYTHON_TOOLCHAIN_TYPE].py3_runtime.files or depset() 22 | toolchain_files = {f: None for f in toolchain_depset.to_list()} 23 | 24 | imports = [] 25 | for dep in ctx.attr.deps: 26 | if PyInfo not in dep: 27 | continue 28 | imports.extend([i for i in dep[PyInfo].imports.to_list() if i not in imports]) 29 | 30 | deps = depset(transitive = [dep[DefaultInfo].default_runfiles.files for dep in ctx.attr.deps]) 31 | data = depset(transitive = [data[DefaultInfo].files for data in ctx.attr.data]) 32 | out = ctx.outputs.output 33 | 34 | files = [] 35 | for dep in deps.to_list() + data.to_list(): 36 | # Skip files that are provided by the python toolchain. 37 | # They don't need to be in the venv. 38 | if dep in toolchain_files: 39 | continue 40 | 41 | typ = "S" if dep.is_source else "G" 42 | files.append({"t": typ, "p": dep.short_path}) 43 | 44 | doc = { 45 | "workspace": ctx.workspace_name, 46 | "imports": imports, 47 | "files": files, 48 | "commands": ctx.attr.commands, 49 | "always_link": ctx.attr.always_link, 50 | } 51 | ctx.actions.write(out, json.encode(doc)) 52 | 53 | return [DefaultInfo(files = depset(direct = [out]))] 54 | 55 | _py_venv_deps = rule( 56 | implementation = _py_venv_deps_impl, 57 | attrs = { 58 | "deps": attr.label_list(), 59 | "data": attr.label_list(), 60 | "commands": attr.string_list(), 61 | "always_link": attr.bool(), 62 | "output": attr.output(), 63 | }, 64 | toolchains = [PYTHON_TOOLCHAIN_TYPE], 65 | ) 66 | 67 | def py_venv(name, deps = None, data = None, extra_pip_commands = None, always_link = False, venv_location = None, **kwargs): 68 | deps = deps or [] 69 | data = data or [] 70 | extra_pip_commands = extra_pip_commands or [] 71 | 72 | deps_name = "_" + name + "_deps" 73 | out_name = deps_name + ".json" 74 | out_label = ":" + out_name 75 | _py_venv_deps( 76 | name = deps_name, 77 | deps = deps, 78 | data = data, 79 | commands = extra_pip_commands, 80 | always_link = always_link, 81 | output = out_name, 82 | **kwargs, 83 | ) 84 | 85 | env = { 86 | "BUILD_ENV_INPUT": "$(location " + out_label + ")", 87 | } 88 | 89 | if venv_location: 90 | env.update({"VENV_LOCATION": venv_location}) 91 | 92 | py_binary( 93 | name = name, 94 | srcs = ["@rules_pyvenv//:build_env.py"], 95 | deps = ["@rules_pyvenv//vendor/importlib_metadata"], 96 | data = [out_label] + deps + data, 97 | main = "@rules_pyvenv//:build_env.py", 98 | env = env, 99 | **kwargs, 100 | ) 101 | --------------------------------------------------------------------------------