diff --git a/.cloudbuild/library_generation/library_generation.Dockerfile b/.cloudbuild/library_generation/library_generation.Dockerfile index 9977160797..2629c092aa 100644 --- a/.cloudbuild/library_generation/library_generation.Dockerfile +++ b/.cloudbuild/library_generation/library_generation.Dockerfile @@ -17,7 +17,6 @@ FROM gcr.io/cloud-devrel-public-resources/python SHELL [ "/bin/bash", "-c" ] -ARG SYNTHTOOL_COMMITTISH=696c4bff721f5541cd75fdc97d413f8f39d2a2c1 ARG OWLBOT_CLI_COMMITTISH=ac84fa5c423a0069bbce3d2d869c9730c8fdf550 ARG PROTOC_VERSION=25.3 ENV HOME=/home @@ -44,19 +43,11 @@ RUN ln -s $(which python3.11) /usr/local/bin/python RUN ln -s $(which python3.11) /usr/local/bin/python3 RUN python -m pip install --upgrade pip -# install scripts as a python package +# install main scripts as a python package WORKDIR /src RUN python -m pip install -r requirements.txt RUN python -m pip install . -# install synthtool -WORKDIR /tools -RUN git clone https://github.com/googleapis/synthtool -WORKDIR /tools/synthtool -RUN git checkout "${SYNTHTOOL_COMMITTISH}" -RUN python3 -m pip install --no-deps -e . -RUN python3 -m pip install -r requirements.in - # Install nvm with node and npm ENV NODE_VERSION 20.12.0 WORKDIR /home diff --git a/.github/snippet-bot.yml b/.github/snippet-bot.yml index 9458774ff0..84a9e461c2 100644 --- a/.github/snippet-bot.yml +++ b/.github/snippet-bot.yml @@ -5,3 +5,4 @@ ignoreFiles: - test/** - showcase/** - library_generation/owlbot/templates/java_library/samples/install-without-bom/pom.xml + - library_generation/owlbot/synthtool/gcp/snippets.py diff --git a/library_generation/owlbot/synthtool/__init__.py b/library_generation/owlbot/synthtool/__init__.py new file mode 100644 index 0000000000..6e5f1f12e2 --- /dev/null +++ b/library_generation/owlbot/synthtool/__init__.py @@ -0,0 +1,34 @@ +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Synthtool synthesizes libraries from disparate sources.""" + +import sys + +from synthtool.transforms import ( + move, + replace, + get_staging_dirs, + remove_staging_dirs, +) + +copy = move + +__all__ = [ + "copy", + "move", + "replace", + "get_staging_dirs", + "remove_staging_dirs", +] diff --git a/library_generation/owlbot/synthtool/_tracked_paths.py b/library_generation/owlbot/synthtool/_tracked_paths.py new file mode 100644 index 0000000000..b72d7037fe --- /dev/null +++ b/library_generation/owlbot/synthtool/_tracked_paths.py @@ -0,0 +1,39 @@ +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Tracked paths. + +This is a bit of a hack (imported from original synthtool library). +""" + +import pathlib + + +_tracked_paths = [] + + +def add(path): + _tracked_paths.append(pathlib.Path(path)) + # Reverse sort the list, so that the deepest paths get matched first. + _tracked_paths.sort(key=lambda s: -len(str(s))) + + +def relativize(path): + path = pathlib.Path(path) + for tracked_path in _tracked_paths: + try: + return path.relative_to(tracked_path) + except ValueError: + pass + raise ValueError(f"The root for {path} is not tracked.") diff --git a/library_generation/owlbot/synthtool/gcp/common.py b/library_generation/owlbot/synthtool/gcp/common.py new file mode 100644 index 0000000000..b115665af6 --- /dev/null +++ b/library_generation/owlbot/synthtool/gcp/common.py @@ -0,0 +1,153 @@ +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import json +import os +import re +import sys +import shutil +import yaml +from pathlib import Path +from typing import Dict, List, Optional +import jinja2 +import logging + +from synthtool import _tracked_paths +from synthtool.sources import templates + +logger = logging.getLogger() +logger.setLevel(logging.DEBUG) + + +DEFAULT_TEMPLATES_PATH = "synthtool/gcp/templates" +LOCAL_TEMPLATES: Optional[str] = os.environ.get("SYNTHTOOL_TEMPLATES") + +# Originally brought from gcp/partials.py. +# These are the default locations to look up +_DEFAULT_PARTIAL_FILES = [ + ".readme-partials.yml", + ".readme-partials.yaml", + ".integration-partials.yaml", +] + + +class CommonTemplates: + def __init__(self, template_path: Optional[Path] = None): + if LOCAL_TEMPLATES is None: + logger.error("env var SYNTHTOOL_TEMPLATES must be set") + sys.exit(1) + logger.debug(f"Using local templates at {LOCAL_TEMPLATES}") + self._template_root = Path(LOCAL_TEMPLATES) + self._templates = templates.Templates(self._template_root) + self.excludes = [] # type: List[str] + + def _generic_library(self, directory: str, relative_dir=None, **kwargs) -> Path: + # load common repo meta information (metadata that's not language specific). + if "metadata" in kwargs: + self._load_generic_metadata(kwargs["metadata"], relative_dir=relative_dir) + # if no samples were found, don't attempt to render a + # samples/README.md. + if "samples" not in kwargs["metadata"] or not kwargs["metadata"]["samples"]: + self.excludes.append("samples/README.md") + + t = templates.TemplateGroup(self._template_root / directory, self.excludes) + + result = t.render(**kwargs) + _tracked_paths.add(result) + + return result + + def java_library(self, **kwargs) -> Path: + # kwargs["metadata"] is required to load values from .repo-metadata.json + if "metadata" not in kwargs: + kwargs["metadata"] = {} + return self._generic_library("java_library", **kwargs) + + def render(self, template_name: str, **kwargs) -> Path: + template = self._templates.render(template_name, **kwargs) + _tracked_paths.add(template) + return template + + def _load_generic_metadata(self, metadata: Dict, relative_dir=None): + """ + loads additional meta information from .repo-metadata.json. + """ + metadata["partials"] = load_partials() + + # Loads repo metadata information from the default location if it + # hasn't already been set. Some callers may have already loaded repo + # metadata, so we don't need to do it again or overwrite it. Also, only + # set the "repo" key. + if "repo" not in metadata: + metadata["repo"] = _load_repo_metadata(relative_dir=relative_dir) + + +def _load_repo_metadata( + relative_dir=None, metadata_file: str = "./.repo-metadata.json" +) -> Dict: + """Parse a metadata JSON file into a Dict. + + Currently, the defined fields are: + * `name` - The service's API name + * `name_pretty` - The service's API title. This will be used for generating titles on READMEs + * `product_documentation` - The product documentation on cloud.google.com + * `client_documentation` - The client library reference documentation + * `issue_tracker` - The public issue tracker for the product + * `release_level` - The release level of the client library. One of: alpha, beta, + ga, deprecated, preview, stable + * `language` - The repo language. One of dotnet, go, java, nodejs, php, python, ruby + * `repo` - The GitHub repo in the format {owner}/{repo} + * `distribution_name` - The language-idiomatic package/distribution name + * `api_id` - The API ID associated with the service. Fully qualified identifier use to + enable a service in the cloud platform (e.g. monitoring.googleapis.com) + * `requires_billing` - Whether or not the API requires billing to be configured on the + customer's acocunt + + Args: + metadata_file (str, optional): Path to the metadata json file + + Returns: + A dictionary of metadata. This may not necessarily include all the defined fields above. + """ + if relative_dir is not None: + if os.path.exists(Path(relative_dir, metadata_file).resolve()): + with open(Path(relative_dir, metadata_file).resolve()) as f: + return json.load(f) + elif os.path.exists(metadata_file): + with open(metadata_file) as f: + return json.load(f) + return {} + + +def load_partials(files: List[str] = []) -> Dict: + """ + hand-crafted artisanal markdown can be provided in a .readme-partials.yml. + The following fields are currently supported: + + body: custom body to include in the usage section of the document. + samples_body: an optional body to place below the table of contents + in samples/README.md. + introduction: a more thorough introduction than metadata["description"]. + title: provide markdown to use as a custom title. + deprecation_warning: a warning to indicate that the library has been + deprecated and a pointer to an alternate option + """ + result: Dict[str, Dict] = {} + cwd_path = Path(os.getcwd()) + for file in files + _DEFAULT_PARTIAL_FILES: + partials_file = cwd_path / file + if os.path.exists(partials_file): + with open(partials_file) as f: + result.update(yaml.load(f, Loader=yaml.SafeLoader)) + return result diff --git a/library_generation/owlbot/synthtool/gcp/samples.py b/library_generation/owlbot/synthtool/gcp/samples.py new file mode 100644 index 0000000000..760fc06574 --- /dev/null +++ b/library_generation/owlbot/synthtool/gcp/samples.py @@ -0,0 +1,91 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import glob +import logging +import re +import os +import yaml +from typing import List, Dict + +logger = logging.getLogger() +logger.setLevel(logging.DEBUG) + + +def _read_sample_metadata_comment(sample_file: str) -> Dict: + """Additional meta-information can be provided through embedded comments: + + // sample-metadata: + // title: ACL (Access Control) + // description: Demonstrates setting access control rules. + // usage: node iam.js --help + """ + sample_metadata = {} # type: Dict[str, str] + with open(sample_file) as f: + contents = f.read() + match = re.search( + r"(?P// *sample-metadata:([^\n]+|\n//)+)", contents, re.DOTALL + ) + if match: + # the metadata yaml is stored in a comments, remove the + # prefix so that we can parse the yaml contained. + sample_metadata_string = re.sub(r"((#|//) ?)", "", match.group("metadata")) + try: + sample_metadata = yaml.load( + sample_metadata_string, Loader=yaml.SafeLoader + )["sample-metadata"] + except yaml.scanner.ScannerError: + # warn and continue on bad metadata + logger.warning(f"bad metadata detected in {sample_file}") + return sample_metadata + + +def _sample_metadata(file: str) -> Dict[str, str]: + metadata = { + "title": _decamelize(os.path.splitext(os.path.basename(file))[0]), + "file": file, + } + return {**metadata, **_read_sample_metadata_comment(file)} + + +def all_samples(sample_globs: List[str]) -> List[Dict[str, str]]: + """Walks samples directory and builds up samples data-structure + + Args: + sample_globs: (List[str]): List of path globs to search for samples + + Returns: + A list of sample metadata in the format: + { + "title": "Requester Pays", + "file": "samples/requesterPays.js" + } + The file path is the relative path from the repository root. + """ + files = [] + for sample_glob in sample_globs: + for file in glob.glob(sample_glob, recursive=True): + files.append(file) + return [_sample_metadata(file) for file in sorted(files)] + + +def _decamelize(value: str): + """Parser to convert fooBar.js to Foo Bar.""" + if not value: + return "" + str_decamelize = re.sub("^.", value[0].upper(), value) # apple -> Apple. + str_decamelize = re.sub( + "([A-Z]+)([A-Z])([a-z0-9])", r"\1 \2\3", str_decamelize + ) # ACLBatman -> ACL Batman. + return re.sub("([a-z0-9])([A-Z])", r"\1 \2", str_decamelize) # FooBar -> Foo Bar. diff --git a/library_generation/owlbot/synthtool/gcp/snippets.py b/library_generation/owlbot/synthtool/gcp/snippets.py new file mode 100644 index 0000000000..5db00656b5 --- /dev/null +++ b/library_generation/owlbot/synthtool/gcp/snippets.py @@ -0,0 +1,124 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import glob +import os +import re +from typing import Dict, List + +OPEN_SNIPPET_REGEX = r".*\[START ([a-z0-9_]+)\].*$" +CLOSE_SNIPPET_REGEX = r".*\[END ([a-z0-9_]+)\].*$" +OPEN_EXCLUDE_REGEX = r".*\[START_EXCLUDE\].*$" +CLOSE_EXCLUDE_REGEX = r".*\[END_EXCLUDE\].*$" + + +def _trim_leading_whitespace(lines: List[str]) -> List[str]: + """Trims leading, plain spaces from the snippet content. Finds the minimum + number of leading spaces, ignoring empty lines, and removes that number of + spaces from each line. + + Args: + lines (List[str]): Lines of content. These lines are newline terminated. + + Returns: + List of trimmed lines. + """ + + def number_of_leading_spaces(input: str) -> int: + return len(input) - len(input.lstrip(" ")) + + def is_empty_line(input: str) -> bool: + if re.match(r"^\s*$", input): + return True + return False + + leading_spaces = [ + number_of_leading_spaces(line) for line in lines if not is_empty_line(line) + ] + max_leading_spaces = min(leading_spaces) + return [ + "\n" if is_empty_line(line) else line[max_leading_spaces:] for line in lines + ] + + +def all_snippets_from_file(sample_file: str) -> Dict[str, str]: + """Reads in a sample file and parse out all contained snippets. + + Args: + sample_file (str): Sample file to parse. + + Returns: + Dictionary of snippet name to snippet code. + """ + if not os.path.exists(sample_file): + return {} + + snippet_lines = {} # type: Dict[str, List[str]] + open_snippets = set() + with open(sample_file) as f: + excluding = False + # Iterate over each line: + # - If the line matches an opening snippet tag, add that snippet tag to + # the set of open tags. + # - If the line matches a closing snippet tag, remove that snippet tag + # from the set of open tags. + # - If the line matches an opening exclude tag, record that we excluding + # content. + # - If the line matches a closing exclude tag, record that we are capturing + # content again. + # - Otherwise, if we are not excluding content, add the line to each of the + # open snippets + # + # This allows us to handle parsing nested or interleaved snippets and ignore + # blocks of code in the snippets + for line in f: + open_match = re.match(pattern=OPEN_SNIPPET_REGEX, string=line) + close_match = re.match(pattern=CLOSE_SNIPPET_REGEX, string=line) + open_exclude_match = re.match(pattern=OPEN_EXCLUDE_REGEX, string=line) + close_exclude_match = re.match(pattern=CLOSE_EXCLUDE_REGEX, string=line) + if open_match and not excluding: + open_snippets.add(open_match[1]) + if not open_match[1] in snippet_lines: + snippet_lines[open_match[1]] = [] + elif close_match and not excluding: + open_snippets.discard(close_match[1]) + elif open_exclude_match: + excluding = True + elif close_exclude_match: + excluding = False + elif not excluding: + for snippet in open_snippets: + snippet_lines[snippet].append(line) + + return { + snippet: "".join(_trim_leading_whitespace(lines)) + for snippet, lines in snippet_lines.items() + } + + +def all_snippets(snippet_globs: List[str]) -> Dict[str, str]: + """Walks the samples directory and parses snippets from each file. + + Args: + snippet_globs (List[str]): List of path globs to expand. + + Returns: + Dictionary of snippet name to snippet code. + """ + snippets = {} + for snippet_glob in snippet_globs: + for file in glob.glob(snippet_glob, recursive=True): + for snippet, code in all_snippets_from_file(file).items(): + snippets[snippet] = code + return snippets diff --git a/library_generation/owlbot/synthtool/languages/java.py b/library_generation/owlbot/synthtool/languages/java.py new file mode 100644 index 0000000000..9e6dcaeed4 --- /dev/null +++ b/library_generation/owlbot/synthtool/languages/java.py @@ -0,0 +1,686 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import glob +import os +import xml.etree.ElementTree as ET +import re +import requests +import yaml +import synthtool as s +import synthtool.gcp as gcp +import logging +from synthtool.gcp import common, samples, snippets +from pathlib import Path +from typing import Any, Optional, Dict, Iterable, List +from datetime import date + +logger = logging.getLogger() +logger.setLevel(logging.DEBUG) + + +JAR_DOWNLOAD_URL = "https://github.com/google/google-java-format/releases/download/google-java-format-{version}/google-java-format-{version}-all-deps.jar" +DEFAULT_FORMAT_VERSION = "1.7" +CURRENT_YEAR = date.today().year +GOOD_LICENSE = f"""/* + * Copyright {CURRENT_YEAR} Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +""" +PROTOBUF_HEADER = "// Generated by the protocol buffer compiler. DO NOT EDIT!" +BAD_LICENSE = """/\\* + \\* Copyright \\d{4} Google LLC + \\* + \\* Licensed under the Apache License, Version 2.0 \\(the "License"\\); you may not use this file except + \\* in compliance with the License. You may obtain a copy of the License at + \\* + \\* http://www.apache.org/licenses/LICENSE-2.0 + \\* + \\* Unless required by applicable law or agreed to in writing, software distributed under the License + \\* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + \\* or implied. See the License for the specific language governing permissions and limitations under + \\* the License. + \\*/ +""" +DEFAULT_MIN_SUPPORTED_JAVA_VERSION = 8 +METADATA = "metadata" +LIBRARIES_BOM_VERSION = "libraries_bom_version" +LIBRARIES_BOM_VERSION_ENV_KEY = "SYNTHTOOL_LIBRARIES_BOM_VERSION" +HEADER_REGEX = re.compile("\\* Copyright \\d{4} Google LLC") +LIBRARY_VERSION = "library_version" +LIBRARY_VERSION_ENV_KEY = "SYNTHTOOL_LIBRARY_VERSION" + + +def _file_has_header(path: Path) -> bool: + """Return true if the file already contains a license header.""" + with open(path, "rt") as fp: + for line in fp: + if HEADER_REGEX.search(line): + return True + return False + + +def _filter_no_header(paths: Iterable[Path]) -> Iterable[Path]: + """Return a subset of files that do not already have a header.""" + for path in paths: + anchor = Path(path.anchor) + remainder = str(path.relative_to(path.anchor)) + for file in anchor.glob(remainder): + if not _file_has_header(file): + yield file + + +def fix_proto_headers(proto_root: Path) -> None: + """Helper to ensure that generated proto classes have appropriate license headers. + + If the file does not already contain a license header, inject one at the top of the file. + Some resource name classes may contain malformed license headers. In those cases, replace + those with our standard license header. + """ + s.replace( + _filter_no_header([proto_root / "src/**/*.java"]), + PROTOBUF_HEADER, + f"{GOOD_LICENSE}{PROTOBUF_HEADER}", + ) + # https://github.com/googleapis/gapic-generator/issues/3074 + s.replace( + [proto_root / "src/**/*Name.java", proto_root / "src/**/*Names.java"], + BAD_LICENSE, + GOOD_LICENSE, + ) + + +def fix_grpc_headers(grpc_root: Path, package_name: str = "unused") -> None: + """Helper to ensure that generated grpc stub classes have appropriate license headers. + + If the file does not already contain a license header, inject one at the top of the file. + """ + s.replace( + _filter_no_header([grpc_root / "src/**/*.java"]), + "^package (.*);", + f"{GOOD_LICENSE}package \\1;", + ) + + +def latest_maven_version(group_id: str, artifact_id: str) -> Optional[str]: + """Helper function to find the latest released version of a Maven artifact. + + Fetches metadata from Maven Central and parses out the latest released + version. + + Args: + group_id (str): The groupId of the Maven artifact + artifact_id (str): The artifactId of the Maven artifact + + Returns: + The latest version of the artifact as a string or None + """ + group_path = "/".join(group_id.split(".")) + url = ( + f"https://repo1.maven.org/maven2/{group_path}/{artifact_id}/maven-metadata.xml" + ) + response = requests.get(url) + if response.status_code >= 400: + return "0.0.0" + + return version_from_maven_metadata(response.text) + + +def version_from_maven_metadata(metadata: str) -> Optional[str]: + """Helper function to parse the latest released version from the Maven + metadata XML file. + + Args: + metadata (str): The XML contents of the Maven metadata file + + Returns: + The latest version of the artifact as a string or None + """ + root = ET.fromstring(metadata) + latest = root.find("./versioning/latest") + if latest is not None: + return latest.text + + return None + + +def _common_generation( + service: str, + version: str, + library: Path, + package_pattern: str, + suffix: str = "", + destination_name: str = None, + cloud_api: bool = True, + diregapic: bool = False, + preserve_gapic: bool = False, +): + """Helper function to execution the common generation cleanup actions. + + Fixes headers for protobuf classes and generated gRPC stub services. Copies + code and samples to their final destinations by convention. Runs the code + formatter on the generated code. + + Args: + service (str): Name of the service. + version (str): Service API version. + library (Path): Path to the temp directory with the generated library. + package_pattern (str): Package name template for fixing file headers. + suffix (str, optional): Suffix that the generated library folder. The + artman output differs from bazel's output directory. Defaults to "". + destination_name (str, optional): Override the service name for the + destination of the output code. Defaults to the service name. + preserve_gapic (bool, optional): Whether to preserve the gapic directory + prefix. Default False. + """ + + if destination_name is None: + destination_name = service + + cloud_prefix = "cloud-" if cloud_api else "" + package_name = package_pattern.format(service=service, version=version) + proto_library_name = f"proto-google-{cloud_prefix}{service}-{version}" + grpc_library_name = f"grpc-google-{cloud_prefix}{service}-{version}" + gapic_library_name = f"gapic-google-{cloud_prefix}{service}-{version}" + fix_proto_headers(library / f"{proto_library_name}{suffix}") + fix_grpc_headers(library / f"{grpc_library_name}{suffix}", package_name) + + if preserve_gapic: + s.copy( + [library / f"{gapic_library_name}{suffix}/src"], + f"{gapic_library_name}/src", + required=True, + ) + else: + s.copy( + [library / f"{gapic_library_name}{suffix}/src"], + f"google-{cloud_prefix}{destination_name}/src", + required=True, + ) + + s.copy( + [library / f"{grpc_library_name}{suffix}/src"], + f"{grpc_library_name}/src", + # For REST-only clients, like java-compute, gRPC artifact does not exist + required=(not diregapic), + ) + s.copy( + [library / f"{proto_library_name}{suffix}/src"], + f"{proto_library_name}/src", + required=True, + ) + + if preserve_gapic: + format_code(f"{gapic_library_name}/src") + else: + format_code(f"google-{cloud_prefix}{destination_name}/src") + format_code(f"{grpc_library_name}/src") + format_code(f"{proto_library_name}/src") + + +def _merge_release_please(destination_text: str): + handle_gh_release_key = "handleGHRelease" + branches_key = "branches" + config = yaml.safe_load(destination_text) + if handle_gh_release_key in config: + return destination_text + + config[handle_gh_release_key] = True + + if branches_key in config: + for branch in config[branches_key]: + branch[handle_gh_release_key] = True + return yaml.dump(config) + + +def _merge_common_templates( + source_text: str, destination_text: str, file_path: Path +) -> str: + # keep any existing pom.xml + if file_path.match("pom.xml") or file_path.match("sync-repo-settings.yaml"): + logger.debug(f"existing pom file found ({file_path}) - keeping the existing") + return destination_text + + if file_path.match("release-please.yml"): + return _merge_release_please(destination_text) + + # by default return the newly generated content + return source_text + + +def _common_template_metadata() -> Dict[str, Any]: + metadata = {} # type: Dict[str, Any] + repo_metadata = common._load_repo_metadata() + if repo_metadata: + metadata["repo"] = repo_metadata + group_id, artifact_id = repo_metadata["distribution_name"].split(":") + + metadata["latest_version"] = latest_maven_version( + group_id=group_id, artifact_id=artifact_id + ) + + metadata["latest_bom_version"] = latest_maven_version( + group_id="com.google.cloud", + artifact_id="libraries-bom", + ) + + metadata["samples"] = samples.all_samples(["samples/**/src/main/java/**/*.java"]) + metadata["snippets"] = snippets.all_snippets( + ["samples/**/src/main/java/**/*.java", "samples/**/pom.xml"] + ) + if repo_metadata and "min_java_version" in repo_metadata: + metadata["min_java_version"] = repo_metadata["min_java_version"] + else: + metadata["min_java_version"] = DEFAULT_MIN_SUPPORTED_JAVA_VERSION + + return metadata + + +def common_templates( + excludes: List[str] = [], + template_path: Optional[Path] = None, + **kwargs, +) -> None: + """Generate common templates for a Java Library + + Fetches information about the repository from the .repo-metadata.json file, + information about the latest artifact versions and copies the files into + their expected location. + + Args: + :param excludes: List of template paths to ignore + :param template_path: + :param kwargs: Additional options for CommonTemplates.java_library() + """ + metadata = _common_template_metadata() + kwargs[METADATA] = metadata + + # Generate flat to tell this repository is a split repo that have migrated + # to monorepo. The owlbot.py in the monorepo sets monorepo=True. + monorepo = kwargs.get("monorepo", False) + kwargs["monorepo"] = monorepo + split_repo = not monorepo + repo_metadata = metadata["repo"] + repo_short = repo_metadata["repo_short"] + if os.getenv(LIBRARIES_BOM_VERSION_ENV_KEY, default=None) is not None: + kwargs[METADATA][LIBRARIES_BOM_VERSION] = os.getenv( + LIBRARIES_BOM_VERSION_ENV_KEY + ) + kwargs[METADATA][LIBRARY_VERSION] = os.getenv(LIBRARY_VERSION_ENV_KEY) + # Special libraries that are not GAPIC_AUTO but in the monorepo + special_libs_in_monorepo = [ + "java-translate", + "java-dns", + "java-notification", + "java-resourcemanager", + ] + kwargs["migrated_split_repo"] = split_repo and ( + repo_metadata["library_type"] == "GAPIC_AUTO" + or (repo_short and repo_short in special_libs_in_monorepo) + ) + logger.info( + "monorepo: {}, split_repo: {}, library_type: {}," + " repo_short: {}, migrated_split_repo: {}".format( + monorepo, + split_repo, + repo_metadata["library_type"], + repo_short, + kwargs["migrated_split_repo"], + ) + ) + + templates = gcp.common.CommonTemplates(template_path=template_path).java_library( + **kwargs + ) + + s.copy([templates], excludes=excludes, merge=_merge_common_templates) + + +def custom_templates(files: List[str], **kwargs) -> None: + """Generate custom template files + + Fetches information about the repository from the .repo-metadata.json file, + information about the latest artifact versions and copies the files into + their expected location. + + Args: + files (List[str], optional): List of template paths to include + **kwargs: Additional options for CommonTemplates.render() + """ + kwargs["metadata"] = _common_template_metadata() + kwargs["metadata"]["partials"] = common.load_partials() + for file in files: + template = gcp.CommonTemplates().render(file, **kwargs) + s.copy([template]) + + +def remove_method(filename: str, signature: str): + """Helper to remove an entire method. + + Goes line-by-line to detect the start of the block. Determines + the end of the block by a closing brace at the same indentation + level. This requires the file to be correctly formatted. + + Example: consider the following class: + + class Example { + public void main(String[] args) { + System.out.println("Hello World"); + } + + public String foo() { + return "bar"; + } + } + + To remove the `main` method above, use: + + remove_method('path/to/file', 'public void main(String[] args)') + + Args: + filename (str): Path to source file + signature (str): Full signature of the method to remove. Example: + `public void main(String[] args)`. + """ + lines = [] + leading_regex = None + with open(filename, "r") as fp: + line = fp.readline() + while line: + # for each line, try to find the matching + regex = re.compile("(\\s*)" + re.escape(signature) + ".*") + match = regex.match(line) + if match: + leading_regex = re.compile(match.group(1) + "}") + line = fp.readline() + continue + + # not in a ignore block - preserve the line + if not leading_regex: + lines.append(line) + line = fp.readline() + continue + + # detect the closing tag based on the leading spaces + match = leading_regex.match(line) + if match: + # block is closed, resume capturing content + leading_regex = None + + line = fp.readline() + + with open(filename, "w") as fp: + for line in lines: + # print(line) + fp.write(line) + + +def copy_and_rename_method(filename: str, signature: str, before: str, after: str): + """Helper to make a copy an entire method and rename it. + + Goes line-by-line to detect the start of the block. Determines + the end of the block by a closing brace at the same indentation + level. This requires the file to be correctly formatted. + The method is copied over and renamed in the method signature. + The calls to both methods are separate and unaffected. + + Example: consider the following class: + + class Example { + public void main(String[] args) { + System.out.println("Hello World"); + } + + public String foo() { + return "bar"; + } + } + + To copy and rename the `main` method above, use: + + copy_and_rename_method('path/to/file', 'public void main(String[] args)', + 'main', 'foo1') + + Args: + filename (str): Path to source file + signature (str): Full signature of the method to remove. Example: + `public void main(String[] args)`. + before (str): name of the method to be copied + after (str): new name of the copied method + """ + lines = [] + method = [] + leading_regex = None + with open(filename, "r") as fp: + line = fp.readline() + while line: + # for each line, try to find the matching + regex = re.compile("(\\s*)" + re.escape(signature) + ".*") + match = regex.match(line) + if match: + leading_regex = re.compile(match.group(1) + "}") + lines.append(line) + method.append(line.replace(before, after)) + line = fp.readline() + continue + + lines.append(line) + # not in a ignore block - preserve the line + if leading_regex: + method.append(line) + else: + line = fp.readline() + continue + + # detect the closing tag based on the leading spaces + match = leading_regex.match(line) + if match: + # block is closed, resume capturing content + leading_regex = None + lines.append("\n") + lines.extend(method) + + line = fp.readline() + + with open(filename, "w") as fp: + for line in lines: + # print(line) + fp.write(line) + + +def add_javadoc(filename: str, signature: str, javadoc_type: str, content: List[str]): + """Helper to add a javadoc annoatation to a method. + + Goes line-by-line to detect the start of the block. + Then finds the existing method comment (if it exists). If the + comment already exists, it will append the javadoc annotation + to the javadoc block. Otherwise, it will create a new javadoc + comment block. + + Example: consider the following class: + + class Example { + public void main(String[] args) { + System.out.println("Hello World"); + } + + public String foo() { + return "bar"; + } + } + + To add a javadoc annotation the `main` method above, use: + + add_javadoc('path/to/file', 'public void main(String[] args)', + 'deprecated', 'Please use foo instead.') + + Args: + filename (str): Path to source file + signature (str): Full signature of the method to remove. Example: + `public void main(String[] args)`. + javadoc_type (str): The type of javadoc annotation. Example: `deprecated`. + content (List[str]): The javadoc lines + """ + lines: List[str] = [] + annotations: List[str] = [] + with open(filename, "r") as fp: + line = fp.readline() + while line: + # for each line, try to find the matching + regex = re.compile("(\\s*)" + re.escape(signature) + ".*") + match = regex.match(line) + if match: + leading_spaces = len(line) - len(line.lstrip()) + indent = leading_spaces * " " + last_line = lines.pop() + while last_line.lstrip() and last_line.lstrip()[0] == "@": + annotations.append(last_line) + last_line = lines.pop() + if last_line.strip() == "*/": + first = True + for content_line in content: + if first: + lines.append( + indent + + " * @" + + javadoc_type + + " " + + content_line + + "\n" + ) + first = False + else: + lines.append(indent + " * " + content_line + "\n") + lines.append(last_line) + else: + lines.append(last_line) + lines.append(indent + "/**\n") + first = True + for content_line in content: + if first: + lines.append( + indent + + " * @" + + javadoc_type + + " " + + content_line + + "\n" + ) + first = False + else: + lines.append(indent + " * " + content_line + "\n") + lines.append(indent + " */\n") + lines.extend(annotations[::-1]) + lines.append(line) + line = fp.readline() + + with open(filename, "w") as fp: + for line in lines: + # print(line) + fp.write(line) + + +def annotate_method(filename: str, signature: str, annotation: str): + """Helper to add an annotation to a method. + + Goes line-by-line to detect the start of the block. + Then adds the annotation above the found method signature. + + Example: consider the following class: + + class Example { + public void main(String[] args) { + System.out.println("Hello World"); + } + + public String foo() { + return "bar"; + } + } + + To add an annotation the `main` method above, use: + + annotate_method('path/to/file', 'public void main(String[] args)', + '@Generated()') + + Args: + filename (str): Path to source file + signature (str): Full signature of the method to remove. Example: + `public void main(String[] args)`. + annotation (str): Full annotation. Example: `@Deprecated` + """ + lines: List[str] = [] + with open(filename, "r") as fp: + line = fp.readline() + while line: + # for each line, try to find the matching + regex = re.compile("(\\s*)" + re.escape(signature) + ".*") + match = regex.match(line) + if match: + leading_spaces = len(line) - len(line.lstrip()) + indent = leading_spaces * " " + lines.append(indent + annotation + "\n") + lines.append(line) + line = fp.readline() + + with open(filename, "w") as fp: + for line in lines: + # print(line) + fp.write(line) + + +def deprecate_method(filename: str, signature: str, alternative: str): + """Helper to deprecate a method. + + Goes line-by-line to detect the start of the block. + Then adds the deprecation comment before the method signature. + The @Deprecation annotation is also added. + + Example: consider the following class: + + class Example { + public void main(String[] args) { + System.out.println("Hello World"); + } + + public String foo() { + return "bar"; + } + } + + To deprecate the `main` method above, use: + + deprecate_method('path/to/file', 'public void main(String[] args)', + DEPRECATION_WARNING.format(new_method="foo")) + + Args: + filename (str): Path to source file + signature (str): Full signature of the method to remove. Example: + `public void main(String[] args)`. + alternative: DEPRECATION WARNING: multiline javadoc comment with user + specified leading open/close comment tags + """ + add_javadoc(filename, signature, "deprecated", alternative.splitlines()) + annotate_method(filename, signature, "@Deprecated") diff --git a/library_generation/owlbot/synthtool/sources/templates.py b/library_generation/owlbot/synthtool/sources/templates.py new file mode 100644 index 0000000000..3ba5391f49 --- /dev/null +++ b/library_generation/owlbot/synthtool/sources/templates.py @@ -0,0 +1,81 @@ +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from typing import Union, List +from pathlib import Path + +import jinja2 +import tempfile +import re + + +PathOrStr = Union[str, Path] + + +def _make_env(location): + env = jinja2.Environment( + loader=jinja2.FileSystemLoader(str(location)), + autoescape=False, + keep_trailing_newline=True, + ) + return env + + +def _render_to_path(env, template_name, dest, params): + template = env.get_template(template_name) + + output = template.stream(**params) + + if template_name.endswith(".j2"): + template_name = template.name[:-3] + + dest = dest / template_name + dest.parent.mkdir(parents=True, exist_ok=True) + + with dest.open("w") as fh: + output.dump(fh) + + # Copy file mode over + source_path = Path(template.filename) + mode = source_path.stat().st_mode + dest.chmod(mode) + + return dest + + +class Templates: + def __init__(self, location: PathOrStr) -> None: + self.env = _make_env(location) + self.source_path = Path(location) + self.dir = Path(tempfile.mkdtemp()) + + def render(self, template_name: str, subdir: PathOrStr = ".", **kwargs) -> Path: + return _render_to_path(self.env, template_name, self.dir / subdir, kwargs) + + +class TemplateGroup: + def __init__(self, location: PathOrStr, excludes: List[str] = []) -> None: + self.env = _make_env(location) + self.dir = Path(tempfile.mkdtemp()) + self.excludes = excludes + + def render(self, subdir: PathOrStr = ".", **kwargs) -> Path: + for template_name in self.env.list_templates(): + if template_name not in self.excludes: + print(template_name) + _render_to_path(self.env, template_name, self.dir / subdir, kwargs) + else: + print(f"Skipping: {template_name}") + + return self.dir diff --git a/library_generation/owlbot/synthtool/transforms.py b/library_generation/owlbot/synthtool/transforms.py new file mode 100644 index 0000000000..cf87995847 --- /dev/null +++ b/library_generation/owlbot/synthtool/transforms.py @@ -0,0 +1,314 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from pathlib import Path +import shutil +from typing import Callable, Iterable, Union, List, Optional +import os +import re +import sys +import logging + +from synthtool import _tracked_paths + +PathOrStr = Union[str, Path] +ListOfPathsOrStrs = Iterable[Union[str, Path]] + +logger = logging.getLogger() +logger.setLevel(logging.DEBUG) + + +class MissingSourceError(Exception): + pass + + +def _expand_paths(paths: ListOfPathsOrStrs, root: PathOrStr = None) -> Iterable[Path]: + """Given a list of globs/paths, expands them into a flat sequence, + expanding globs as necessary.""" + if paths is None: + return [] + + if isinstance(paths, (str, Path)): + paths = [paths] + + if root is None: + root = Path(".") + + # ensure root is a path + root = Path(root) + + # record name of synth script so we don't try to do transforms on it + synth_script_name = sys.argv[0] + + for path in paths: + if isinstance(path, Path): + if path.is_absolute(): + anchor = Path(path.anchor) + remainder = str(path.relative_to(path.anchor)) + yield from anchor.glob(remainder) + else: + yield from root.glob(str(path)) + else: + yield from ( + p + for p in root.glob(path) + if p.absolute() != Path(synth_script_name).absolute() + ) + + +def _filter_files(paths: Iterable[Path]) -> Iterable[Path]: + """Returns only the paths that are files (no directories).""" + + return (path for path in paths if path.is_file() and os.access(path, os.W_OK)) + + +def _merge_file( + source_path: Path, dest_path: Path, merge: Callable[[str, str, Path], str] +): + """ + Writes to the destination the result of merging the source with the + existing destination contents, using the given merge function. + + The merge function must take three arguments: the source contents, the + old destination contents, and a Path to the file to be written. + """ + + with source_path.open("r") as source_file: + source_text = source_file.read() + + with dest_path.open("r+") as dest_file: + dest_text = dest_file.read() + + final_text = merge(source_text, dest_text, dest_path) + + # use the source file's file permission mode + os.chmod(dest_path, os.stat(source_path).st_mode) + if final_text != dest_text: + dest_file.seek(0) + dest_file.write(final_text) + dest_file.truncate() + else: + dest_path.touch() + + +def _copy_dir_to_existing_dir( + source: Path, + destination: Path, + excludes: ListOfPathsOrStrs = None, + merge: Callable[[str, str, Path], str] = None, +) -> bool: + """ + copies files over existing files to an existing directory + this function does not copy empty directories. + + Returns: True if any files were copied, False otherwise. + """ + copied = False + + if not excludes: + excludes = [] + for root, _, files in os.walk(source): + for name in files: + rel_path = str(Path(root).relative_to(source)) + dest_dir = destination / rel_path + dest_path = dest_dir / name + exclude = [ + e + for e in excludes + if ( + Path(e) == _tracked_paths.relativize(root) + or Path(e) == _tracked_paths.relativize(Path(root) / name) + ) + ] + if not exclude: + os.makedirs(str(dest_dir), exist_ok=True) + source_path = Path(os.path.join(root, name)) + if merge is not None and dest_path.is_file(): + try: + _merge_file(source_path, dest_path, merge) + except Exception: + logger.exception( + "_merge_file failed for %s, fall back to copy", + source_path, + ) + shutil.copy2(str(source_path), str(dest_path)) + else: + shutil.copy2(str(source_path), str(dest_path)) + copied = True + + return copied + + +def move( + sources: ListOfPathsOrStrs, + destination: PathOrStr = None, + excludes: ListOfPathsOrStrs = None, + merge: Callable[[str, str, Path], str] = None, + required: bool = False, +) -> bool: + """ + copy file(s) at source to current directory, preserving file mode. + + Args: + sources (ListOfPathsOrStrs): Glob pattern(s) to copy + destination (PathOrStr): Destination folder for copied files + excludes (ListOfPathsOrStrs): Glob pattern(s) of files to skip + merge (Callable[[str, str, Path], str]): Callback function for merging files + if there is an existing file. + required (bool): If required and no source files are copied, throws a MissingSourceError + + Returns: + True if any files were copied, False otherwise. + """ + copied = False + + for source in _expand_paths(sources): + if destination is None: + canonical_destination = _tracked_paths.relativize(source) + else: + canonical_destination = Path(destination) + + if excludes: + excludes = [ + _tracked_paths.relativize(e) for e in _expand_paths(excludes, source) + ] + else: + excludes = [] + if source.is_dir(): + copied = copied or _copy_dir_to_existing_dir( + source, canonical_destination, excludes=excludes, merge=merge + ) + elif source not in excludes: + # copy individual file + if merge is not None and canonical_destination.is_file(): + try: + _merge_file(source, canonical_destination, merge) + except Exception: + logger.exception( + "_merge_file failed for %s, fall back to copy", source + ) + shutil.copy2(source, canonical_destination) + else: + shutil.copy2(source, canonical_destination) + copied = True + + if not copied: + if required: + raise MissingSourceError( + f"No files in sources {sources} were copied. Does the source " + f"contain files?" + ) + else: + logger.warning( + f"No files in sources {sources} were copied. Does the source " + f"contain files?" + ) + + return copied + + +def _replace_in_file(path, expr, replacement): + try: + with path.open("r+") as fh: + return _replace_in_file_handle(fh, expr, replacement) + except UnicodeDecodeError: + pass # It's a binary file. Try again with a binary regular expression. + flags = expr.flags & ~re.UNICODE + expr = re.compile(expr.pattern.encode(), flags) + with path.open("rb+") as fh: + return _replace_in_file_handle(fh, expr, replacement.encode()) + + +def _replace_in_file_handle(fh, expr, replacement): + content = fh.read() + content, count = expr.subn(replacement, content) + + # Don't bother writing the file if we didn't change + # anything. + if count: + fh.seek(0) + fh.write(content) + fh.truncate() + return count + + +def replace( + sources: ListOfPathsOrStrs, before: str, after: str, flags: int = re.MULTILINE +) -> int: + """Replaces occurrences of before with after in all the given sources. + + Returns: + The number of times the text was found and replaced across all files. + """ + expr = re.compile(before, flags=flags or 0) + paths = _filter_files(_expand_paths(sources, ".")) + + if not paths: + logger.warning(f"No files were found in sources {sources} for replace()") + + count_replaced = 0 + for path in paths: + replaced = _replace_in_file(path, expr, after) + count_replaced += replaced + if replaced: + logger.info(f"Replaced {before!r} in {path}.") + + if not count_replaced: + logger.warning( + f"No replacements made in {sources} for pattern {before}, maybe " + "replacement is no longer needed?" + ) + return count_replaced + + +def get_staging_dirs( + default_version: Optional[str] = None, staging_path: Optional[str] = None +) -> List[Path]: + """Returns the list of directories, one per version, copied from + https://github.com/googleapis/googleapis-gen. Will return in lexical sorting + order with the exception of the default_version which will be last (if specified). + + Args: + default_version: the default version of the API. The directory for this version + will be the last item in the returned list if specified. + staging_path: the path to the staging directory. + + Returns: the empty list if no file were copied. + """ + + if staging_path: + staging = Path(staging_path) + else: + staging = Path("owl-bot-staging") + if staging.is_dir(): + # Collect the subdirectories of the staging directory. + versions = [v.name for v in staging.iterdir() if v.is_dir()] + # Reorder the versions so the default version always comes last. + versions = [v for v in versions if v != default_version] + versions.sort() + if default_version is not None: + versions += [default_version] + dirs = [staging / v for v in versions] + for dir in dirs: + _tracked_paths.add(dir) + return dirs + else: + return [] + + +def remove_staging_dirs(): + """Removes all the staging directories.""" + staging = Path("owl-bot-staging") + if staging.is_dir(): + shutil.rmtree(staging) diff --git a/library_generation/requirements.in b/library_generation/requirements.in new file mode 100644 index 0000000000..0e2b25238d --- /dev/null +++ b/library_generation/requirements.in @@ -0,0 +1,28 @@ +# add new packages here and run `pip-compile requirements.in --generate-hashes` to +# generate an entry with hashes in requirements.txt +absl-py==2.1.0 +attr==0.3.2 +attrs==23.2.0 +black==24.4.2 +click==8.1.7 +gitdb==4.0.11 +GitPython==3.1.43 +lxml==5.2.2 +MarkupSafe==2.1.5 +mypy-extensions==1.0.0 +packaging==23.2 +pathspec==0.12.1 +PyYAML==6.0.1 +smmap==5.0.1 +typing==3.7.4.3 +parameterized==0.9.0 # used in parameterized test +colorlog==6.8.2 +watchdog==4.0.1 +nox==2024.4.15 +requests==2.32.3 +setuptools==65.5.1 +jinja2==3.1.4 +# typing-extensions is a transitive dependency. If we run `pip-compile ... --generate-hashes` it will produce +# a list where typing extensions is pinned to >=4.0.1. This will produce an error saying "all requirements +# must have their versions pinned with ==". The following line pins the dependency to a specific version via == +typing-extensions==4.0.1 diff --git a/library_generation/requirements.txt b/library_generation/requirements.txt index d31cff9fca..01e3891312 100644 --- a/library_generation/requirements.txt +++ b/library_generation/requirements.txt @@ -1,19 +1,539 @@ -absl-py==2.1.0 -attr==0.3.2 -attrs==23.2.0 -black==24.4.2 -click==8.1.7 -gitdb==4.0.11 -GitPython==3.1.43 -Jinja2==3.1.4 -lxml==5.2.2 -MarkupSafe==2.1.5 -mypy-extensions==1.0.0 -packaging==23.2 -pathspec==0.12.1 -PyYAML==6.0.1 -smmap==5.0.1 -typing==3.7.4.3 -parameterized==0.9.0 # used in parameterized test -colorlog==6.8.2 -watchdog==4.0.1 +# +# This file is autogenerated by pip-compile with Python 3.11 +# by the following command: +# +# pip-compile --generate-hashes requirements.in +# +absl-py==2.1.0 \ + --hash=sha256:526a04eadab8b4ee719ce68f204172ead1027549089702d99b9059f129ff1308 \ + --hash=sha256:7820790efbb316739cde8b4e19357243fc3608a152024288513dd968d7d959ff + # via -r requirements.in +argcomplete==3.4.0 \ + --hash=sha256:69a79e083a716173e5532e0fa3bef45f793f4e61096cf52b5a42c0211c8b8aa5 \ + --hash=sha256:c2abcdfe1be8ace47ba777d4fce319eb13bf8ad9dace8d085dcad6eded88057f + # via nox +attr==0.3.2 \ + --hash=sha256:1ceebca768181cdcce9827611b1d728e592be5d293911539ea3d0b0bfa1146f4 \ + --hash=sha256:4f4bffeea8c27387bde446675a7ac24f3b8fea1075f12d849b5f5c5181fc8336 + # via -r requirements.in +attrs==23.2.0 \ + --hash=sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30 \ + --hash=sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1 + # via -r requirements.in +black==24.4.2 \ + --hash=sha256:257d724c2c9b1660f353b36c802ccece186a30accc7742c176d29c146df6e474 \ + --hash=sha256:37aae07b029fa0174d39daf02748b379399b909652a806e5708199bd93899da1 \ + --hash=sha256:415e686e87dbbe6f4cd5ef0fbf764af7b89f9057b97c908742b6008cc554b9c0 \ + --hash=sha256:48a85f2cb5e6799a9ef05347b476cce6c182d6c71ee36925a6c194d074336ef8 \ + --hash=sha256:7768a0dbf16a39aa5e9a3ded568bb545c8c2727396d063bbaf847df05b08cd96 \ + --hash=sha256:7e122b1c4fb252fd85df3ca93578732b4749d9be076593076ef4d07a0233c3e1 \ + --hash=sha256:88c57dc656038f1ab9f92b3eb5335ee9b021412feaa46330d5eba4e51fe49b04 \ + --hash=sha256:8e537d281831ad0e71007dcdcbe50a71470b978c453fa41ce77186bbe0ed6021 \ + --hash=sha256:98e123f1d5cfd42f886624d84464f7756f60ff6eab89ae845210631714f6db94 \ + --hash=sha256:accf49e151c8ed2c0cdc528691838afd217c50412534e876a19270fea1e28e2d \ + --hash=sha256:b1530ae42e9d6d5b670a34db49a94115a64596bc77710b1d05e9801e62ca0a7c \ + --hash=sha256:b9176b9832e84308818a99a561e90aa479e73c523b3f77afd07913380ae2eab7 \ + --hash=sha256:bdde6f877a18f24844e381d45e9947a49e97933573ac9d4345399be37621e26c \ + --hash=sha256:be8bef99eb46d5021bf053114442914baeb3649a89dc5f3a555c88737e5e98fc \ + --hash=sha256:bf10f7310db693bb62692609b397e8d67257c55f949abde4c67f9cc574492cc7 \ + --hash=sha256:c872b53057f000085da66a19c55d68f6f8ddcac2642392ad3a355878406fbd4d \ + --hash=sha256:d36ed1124bb81b32f8614555b34cc4259c3fbc7eec17870e8ff8ded335b58d8c \ + --hash=sha256:da33a1a5e49c4122ccdfd56cd021ff1ebc4a1ec4e2d01594fef9b6f267a9e741 \ + --hash=sha256:dd1b5a14e417189db4c7b64a6540f31730713d173f0b63e55fabd52d61d8fdce \ + --hash=sha256:e151054aa00bad1f4e1f04919542885f89f5f7d086b8a59e5000e6c616896ffb \ + --hash=sha256:eaea3008c281f1038edb473c1aa8ed8143a5535ff18f978a318f10302b254063 \ + --hash=sha256:ef703f83fc32e131e9bcc0a5094cfe85599e7109f896fe8bc96cc402f3eb4b6e + # via -r requirements.in +certifi==2024.7.4 \ + --hash=sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b \ + --hash=sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90 + # via requests +charset-normalizer==3.3.2 \ + --hash=sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027 \ + --hash=sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087 \ + --hash=sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786 \ + --hash=sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8 \ + --hash=sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09 \ + --hash=sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185 \ + --hash=sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574 \ + --hash=sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e \ + --hash=sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519 \ + --hash=sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898 \ + --hash=sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269 \ + --hash=sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3 \ + --hash=sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f \ + --hash=sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6 \ + --hash=sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8 \ + --hash=sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a \ + --hash=sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73 \ + --hash=sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc \ + --hash=sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714 \ + --hash=sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2 \ + --hash=sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc \ + --hash=sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce \ + --hash=sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d \ + --hash=sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e \ + --hash=sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6 \ + --hash=sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269 \ + --hash=sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96 \ + --hash=sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d \ + --hash=sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a \ + --hash=sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4 \ + --hash=sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77 \ + --hash=sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d \ + --hash=sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0 \ + --hash=sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed \ + --hash=sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068 \ + --hash=sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac \ + --hash=sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25 \ + --hash=sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8 \ + --hash=sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab \ + --hash=sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26 \ + --hash=sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2 \ + --hash=sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db \ + --hash=sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f \ + --hash=sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5 \ + --hash=sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99 \ + --hash=sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c \ + --hash=sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d \ + --hash=sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811 \ + --hash=sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa \ + --hash=sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a \ + --hash=sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03 \ + --hash=sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b \ + --hash=sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04 \ + --hash=sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c \ + --hash=sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001 \ + --hash=sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458 \ + --hash=sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389 \ + --hash=sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99 \ + --hash=sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985 \ + --hash=sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537 \ + --hash=sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238 \ + --hash=sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f \ + --hash=sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d \ + --hash=sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796 \ + --hash=sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a \ + --hash=sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143 \ + --hash=sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8 \ + --hash=sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c \ + --hash=sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5 \ + --hash=sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5 \ + --hash=sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711 \ + --hash=sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4 \ + --hash=sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6 \ + --hash=sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c \ + --hash=sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7 \ + --hash=sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4 \ + --hash=sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b \ + --hash=sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae \ + --hash=sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12 \ + --hash=sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c \ + --hash=sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae \ + --hash=sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8 \ + --hash=sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887 \ + --hash=sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b \ + --hash=sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4 \ + --hash=sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f \ + --hash=sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5 \ + --hash=sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33 \ + --hash=sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519 \ + --hash=sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561 + # via requests +click==8.1.7 \ + --hash=sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28 \ + --hash=sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de + # via + # -r requirements.in + # black +colorlog==6.8.2 \ + --hash=sha256:3e3e079a41feb5a1b64f978b5ea4f46040a94f11f0e8bbb8261e3dbbeca64d44 \ + --hash=sha256:4dcbb62368e2800cb3c5abd348da7e53f6c362dda502ec27c560b2e58a66bd33 + # via + # -r requirements.in + # nox +distlib==0.3.8 \ + --hash=sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784 \ + --hash=sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64 + # via virtualenv +filelock==3.15.4 \ + --hash=sha256:2207938cbc1844345cb01a5a95524dae30f0ce089eba5b00378295a17e3e90cb \ + --hash=sha256:6ca1fffae96225dab4c6eaf1c4f4f28cd2568d3ec2a44e15a08520504de468e7 + # via virtualenv +gitdb==4.0.11 \ + --hash=sha256:81a3407ddd2ee8df444cbacea00e2d038e40150acfa3001696fe0dcf1d3adfa4 \ + --hash=sha256:bf5421126136d6d0af55bc1e7c1af1c397a34f5b7bd79e776cd3e89785c2b04b + # via + # -r requirements.in + # gitpython +gitpython==3.1.43 \ + --hash=sha256:35f314a9f878467f5453cc1fee295c3e18e52f1b99f10f6cf5b1682e968a9e7c \ + --hash=sha256:eec7ec56b92aad751f9912a73404bc02ba212a23adb2c7098ee668417051a1ff + # via -r requirements.in +idna==3.7 \ + --hash=sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc \ + --hash=sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0 + # via requests +jinja2==3.1.4 \ + --hash=sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369 \ + --hash=sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d + # via -r requirements.in +lxml==5.2.2 \ + --hash=sha256:02437fb7308386867c8b7b0e5bc4cd4b04548b1c5d089ffb8e7b31009b961dc3 \ + --hash=sha256:02f6a8eb6512fdc2fd4ca10a49c341c4e109aa6e9448cc4859af5b949622715a \ + --hash=sha256:05f8757b03208c3f50097761be2dea0aba02e94f0dc7023ed73a7bb14ff11eb0 \ + --hash=sha256:06668e39e1f3c065349c51ac27ae430719d7806c026fec462e5693b08b95696b \ + --hash=sha256:07542787f86112d46d07d4f3c4e7c760282011b354d012dc4141cc12a68cef5f \ + --hash=sha256:08ea0f606808354eb8f2dfaac095963cb25d9d28e27edcc375d7b30ab01abbf6 \ + --hash=sha256:0969e92af09c5687d769731e3f39ed62427cc72176cebb54b7a9d52cc4fa3b73 \ + --hash=sha256:0a028b61a2e357ace98b1615fc03f76eb517cc028993964fe08ad514b1e8892d \ + --hash=sha256:0b3f5016e00ae7630a4b83d0868fca1e3d494c78a75b1c7252606a3a1c5fc2ad \ + --hash=sha256:13e69be35391ce72712184f69000cda04fc89689429179bc4c0ae5f0b7a8c21b \ + --hash=sha256:16a8326e51fcdffc886294c1e70b11ddccec836516a343f9ed0f82aac043c24a \ + --hash=sha256:19b4e485cd07b7d83e3fe3b72132e7df70bfac22b14fe4bf7a23822c3a35bff5 \ + --hash=sha256:1a2569a1f15ae6c8c64108a2cd2b4a858fc1e13d25846be0666fc144715e32ab \ + --hash=sha256:1a7aca7964ac4bb07680d5c9d63b9d7028cace3e2d43175cb50bba8c5ad33316 \ + --hash=sha256:1b590b39ef90c6b22ec0be925b211298e810b4856909c8ca60d27ffbca6c12e6 \ + --hash=sha256:1d8a701774dfc42a2f0b8ccdfe7dbc140500d1049e0632a611985d943fcf12df \ + --hash=sha256:1e275ea572389e41e8b039ac076a46cb87ee6b8542df3fff26f5baab43713bca \ + --hash=sha256:2304d3c93f2258ccf2cf7a6ba8c761d76ef84948d87bf9664e14d203da2cd264 \ + --hash=sha256:23441e2b5339bc54dc949e9e675fa35efe858108404ef9aa92f0456929ef6fe8 \ + --hash=sha256:23cfafd56887eaed93d07bc4547abd5e09d837a002b791e9767765492a75883f \ + --hash=sha256:28bf95177400066596cdbcfc933312493799382879da504633d16cf60bba735b \ + --hash=sha256:2eb2227ce1ff998faf0cd7fe85bbf086aa41dfc5af3b1d80867ecfe75fb68df3 \ + --hash=sha256:2fb0ba3e8566548d6c8e7dd82a8229ff47bd8fb8c2da237607ac8e5a1b8312e5 \ + --hash=sha256:303f540ad2dddd35b92415b74b900c749ec2010e703ab3bfd6660979d01fd4ed \ + --hash=sha256:339ee4a4704bc724757cd5dd9dc8cf4d00980f5d3e6e06d5847c1b594ace68ab \ + --hash=sha256:33ce9e786753743159799fdf8e92a5da351158c4bfb6f2db0bf31e7892a1feb5 \ + --hash=sha256:343ab62e9ca78094f2306aefed67dcfad61c4683f87eee48ff2fd74902447726 \ + --hash=sha256:34e17913c431f5ae01d8658dbf792fdc457073dcdfbb31dc0cc6ab256e664a8d \ + --hash=sha256:364d03207f3e603922d0d3932ef363d55bbf48e3647395765f9bfcbdf6d23632 \ + --hash=sha256:38b67afb0a06b8575948641c1d6d68e41b83a3abeae2ca9eed2ac59892b36706 \ + --hash=sha256:3a745cc98d504d5bd2c19b10c79c61c7c3df9222629f1b6210c0368177589fb8 \ + --hash=sha256:3b019d4ee84b683342af793b56bb35034bd749e4cbdd3d33f7d1107790f8c472 \ + --hash=sha256:3b6a30a9ab040b3f545b697cb3adbf3696c05a3a68aad172e3fd7ca73ab3c835 \ + --hash=sha256:3d1e35572a56941b32c239774d7e9ad724074d37f90c7a7d499ab98761bd80cf \ + --hash=sha256:3d98de734abee23e61f6b8c2e08a88453ada7d6486dc7cdc82922a03968928db \ + --hash=sha256:453d037e09a5176d92ec0fd282e934ed26d806331a8b70ab431a81e2fbabf56d \ + --hash=sha256:45f9494613160d0405682f9eee781c7e6d1bf45f819654eb249f8f46a2c22545 \ + --hash=sha256:4820c02195d6dfb7b8508ff276752f6b2ff8b64ae5d13ebe02e7667e035000b9 \ + --hash=sha256:49095a38eb333aaf44c06052fd2ec3b8f23e19747ca7ec6f6c954ffea6dbf7be \ + --hash=sha256:4aefd911793b5d2d7a921233a54c90329bf3d4a6817dc465f12ffdfe4fc7b8fe \ + --hash=sha256:4bc6cb140a7a0ad1f7bc37e018d0ed690b7b6520ade518285dc3171f7a117905 \ + --hash=sha256:4c30a2f83677876465f44c018830f608fa3c6a8a466eb223535035fbc16f3438 \ + --hash=sha256:50127c186f191b8917ea2fb8b206fbebe87fd414a6084d15568c27d0a21d60db \ + --hash=sha256:50ccb5d355961c0f12f6cf24b7187dbabd5433f29e15147a67995474f27d1776 \ + --hash=sha256:519895c99c815a1a24a926d5b60627ce5ea48e9f639a5cd328bda0515ea0f10c \ + --hash=sha256:54401c77a63cc7d6dc4b4e173bb484f28a5607f3df71484709fe037c92d4f0ed \ + --hash=sha256:546cf886f6242dff9ec206331209db9c8e1643ae642dea5fdbecae2453cb50fd \ + --hash=sha256:55ce6b6d803890bd3cc89975fca9de1dff39729b43b73cb15ddd933b8bc20484 \ + --hash=sha256:56793b7a1a091a7c286b5f4aa1fe4ae5d1446fe742d00cdf2ffb1077865db10d \ + --hash=sha256:57f0a0bbc9868e10ebe874e9f129d2917750adf008fe7b9c1598c0fbbfdde6a6 \ + --hash=sha256:5b8c041b6265e08eac8a724b74b655404070b636a8dd6d7a13c3adc07882ef30 \ + --hash=sha256:5e097646944b66207023bc3c634827de858aebc226d5d4d6d16f0b77566ea182 \ + --hash=sha256:60499fe961b21264e17a471ec296dcbf4365fbea611bf9e303ab69db7159ce61 \ + --hash=sha256:610b5c77428a50269f38a534057444c249976433f40f53e3b47e68349cca1425 \ + --hash=sha256:625e3ef310e7fa3a761d48ca7ea1f9d8718a32b1542e727d584d82f4453d5eeb \ + --hash=sha256:657a972f46bbefdbba2d4f14413c0d079f9ae243bd68193cb5061b9732fa54c1 \ + --hash=sha256:69ab77a1373f1e7563e0fb5a29a8440367dec051da6c7405333699d07444f511 \ + --hash=sha256:6a520b4f9974b0a0a6ed73c2154de57cdfd0c8800f4f15ab2b73238ffed0b36e \ + --hash=sha256:6d68ce8e7b2075390e8ac1e1d3a99e8b6372c694bbe612632606d1d546794207 \ + --hash=sha256:6dcc3d17eac1df7859ae01202e9bb11ffa8c98949dcbeb1069c8b9a75917e01b \ + --hash=sha256:6dfdc2bfe69e9adf0df4915949c22a25b39d175d599bf98e7ddf620a13678585 \ + --hash=sha256:739e36ef7412b2bd940f75b278749106e6d025e40027c0b94a17ef7968d55d56 \ + --hash=sha256:7429e7faa1a60cad26ae4227f4dd0459efde239e494c7312624ce228e04f6391 \ + --hash=sha256:74da9f97daec6928567b48c90ea2c82a106b2d500f397eeb8941e47d30b1ca85 \ + --hash=sha256:74e4f025ef3db1c6da4460dd27c118d8cd136d0391da4e387a15e48e5c975147 \ + --hash=sha256:75a9632f1d4f698b2e6e2e1ada40e71f369b15d69baddb8968dcc8e683839b18 \ + --hash=sha256:76acba4c66c47d27c8365e7c10b3d8016a7da83d3191d053a58382311a8bf4e1 \ + --hash=sha256:79d1fb9252e7e2cfe4de6e9a6610c7cbb99b9708e2c3e29057f487de5a9eaefa \ + --hash=sha256:7ce7ad8abebe737ad6143d9d3bf94b88b93365ea30a5b81f6877ec9c0dee0a48 \ + --hash=sha256:7ed07b3062b055d7a7f9d6557a251cc655eed0b3152b76de619516621c56f5d3 \ + --hash=sha256:7ff762670cada8e05b32bf1e4dc50b140790909caa8303cfddc4d702b71ea184 \ + --hash=sha256:8268cbcd48c5375f46e000adb1390572c98879eb4f77910c6053d25cc3ac2c67 \ + --hash=sha256:875a3f90d7eb5c5d77e529080d95140eacb3c6d13ad5b616ee8095447b1d22e7 \ + --hash=sha256:89feb82ca055af0fe797a2323ec9043b26bc371365847dbe83c7fd2e2f181c34 \ + --hash=sha256:8a7e24cb69ee5f32e003f50e016d5fde438010c1022c96738b04fc2423e61706 \ + --hash=sha256:8ab6a358d1286498d80fe67bd3d69fcbc7d1359b45b41e74c4a26964ca99c3f8 \ + --hash=sha256:8b8df03a9e995b6211dafa63b32f9d405881518ff1ddd775db4e7b98fb545e1c \ + --hash=sha256:8cf85a6e40ff1f37fe0f25719aadf443686b1ac7652593dc53c7ef9b8492b115 \ + --hash=sha256:8e8d351ff44c1638cb6e980623d517abd9f580d2e53bfcd18d8941c052a5a009 \ + --hash=sha256:9164361769b6ca7769079f4d426a41df6164879f7f3568be9086e15baca61466 \ + --hash=sha256:96e85aa09274955bb6bd483eaf5b12abadade01010478154b0ec70284c1b1526 \ + --hash=sha256:981a06a3076997adf7c743dcd0d7a0415582661e2517c7d961493572e909aa1d \ + --hash=sha256:9cd5323344d8ebb9fb5e96da5de5ad4ebab993bbf51674259dbe9d7a18049525 \ + --hash=sha256:9d6c6ea6a11ca0ff9cd0390b885984ed31157c168565702959c25e2191674a14 \ + --hash=sha256:a02d3c48f9bb1e10c7788d92c0c7db6f2002d024ab6e74d6f45ae33e3d0288a3 \ + --hash=sha256:a233bb68625a85126ac9f1fc66d24337d6e8a0f9207b688eec2e7c880f012ec0 \ + --hash=sha256:a2f6a1bc2460e643785a2cde17293bd7a8f990884b822f7bca47bee0a82fc66b \ + --hash=sha256:a6d17e0370d2516d5bb9062c7b4cb731cff921fc875644c3d751ad857ba9c5b1 \ + --hash=sha256:a6d2092797b388342c1bc932077ad232f914351932353e2e8706851c870bca1f \ + --hash=sha256:ab67ed772c584b7ef2379797bf14b82df9aa5f7438c5b9a09624dd834c1c1aaf \ + --hash=sha256:ac6540c9fff6e3813d29d0403ee7a81897f1d8ecc09a8ff84d2eea70ede1cdbf \ + --hash=sha256:ae4073a60ab98529ab8a72ebf429f2a8cc612619a8c04e08bed27450d52103c0 \ + --hash=sha256:ae791f6bd43305aade8c0e22f816b34f3b72b6c820477aab4d18473a37e8090b \ + --hash=sha256:aef5474d913d3b05e613906ba4090433c515e13ea49c837aca18bde190853dff \ + --hash=sha256:b0b3f2df149efb242cee2ffdeb6674b7f30d23c9a7af26595099afaf46ef4e88 \ + --hash=sha256:b128092c927eaf485928cec0c28f6b8bead277e28acf56800e972aa2c2abd7a2 \ + --hash=sha256:b16db2770517b8799c79aa80f4053cd6f8b716f21f8aca962725a9565ce3ee40 \ + --hash=sha256:b336b0416828022bfd5a2e3083e7f5ba54b96242159f83c7e3eebaec752f1716 \ + --hash=sha256:b47633251727c8fe279f34025844b3b3a3e40cd1b198356d003aa146258d13a2 \ + --hash=sha256:b537bd04d7ccd7c6350cdaaaad911f6312cbd61e6e6045542f781c7f8b2e99d2 \ + --hash=sha256:b5e4ef22ff25bfd4ede5f8fb30f7b24446345f3e79d9b7455aef2836437bc38a \ + --hash=sha256:b74b9ea10063efb77a965a8d5f4182806fbf59ed068b3c3fd6f30d2ac7bee734 \ + --hash=sha256:bb2dc4898180bea79863d5487e5f9c7c34297414bad54bcd0f0852aee9cfdb87 \ + --hash=sha256:bbc4b80af581e18568ff07f6395c02114d05f4865c2812a1f02f2eaecf0bfd48 \ + --hash=sha256:bcc98f911f10278d1daf14b87d65325851a1d29153caaf146877ec37031d5f36 \ + --hash=sha256:be49ad33819d7dcc28a309b86d4ed98e1a65f3075c6acd3cd4fe32103235222b \ + --hash=sha256:bec4bd9133420c5c52d562469c754f27c5c9e36ee06abc169612c959bd7dbb07 \ + --hash=sha256:c2faf60c583af0d135e853c86ac2735ce178f0e338a3c7f9ae8f622fd2eb788c \ + --hash=sha256:c689d0d5381f56de7bd6966a4541bff6e08bf8d3871bbd89a0c6ab18aa699573 \ + --hash=sha256:c7079d5eb1c1315a858bbf180000757db8ad904a89476653232db835c3114001 \ + --hash=sha256:cb3942960f0beb9f46e2a71a3aca220d1ca32feb5a398656be934320804c0df9 \ + --hash=sha256:cd9e78285da6c9ba2d5c769628f43ef66d96ac3085e59b10ad4f3707980710d3 \ + --hash=sha256:cf2a978c795b54c539f47964ec05e35c05bd045db5ca1e8366988c7f2fe6b3ce \ + --hash=sha256:d14a0d029a4e176795cef99c056d58067c06195e0c7e2dbb293bf95c08f772a3 \ + --hash=sha256:d237ba6664b8e60fd90b8549a149a74fcc675272e0e95539a00522e4ca688b04 \ + --hash=sha256:d26a618ae1766279f2660aca0081b2220aca6bd1aa06b2cf73f07383faf48927 \ + --hash=sha256:d28cb356f119a437cc58a13f8135ab8a4c8ece18159eb9194b0d269ec4e28083 \ + --hash=sha256:d4ed0c7cbecde7194cd3228c044e86bf73e30a23505af852857c09c24e77ec5d \ + --hash=sha256:d83e2d94b69bf31ead2fa45f0acdef0757fa0458a129734f59f67f3d2eb7ef32 \ + --hash=sha256:d8bbcd21769594dbba9c37d3c819e2d5847656ca99c747ddb31ac1701d0c0ed9 \ + --hash=sha256:d9b342c76003c6b9336a80efcc766748a333573abf9350f4094ee46b006ec18f \ + --hash=sha256:dc911208b18842a3a57266d8e51fc3cfaccee90a5351b92079beed912a7914c2 \ + --hash=sha256:dfa7c241073d8f2b8e8dbc7803c434f57dbb83ae2a3d7892dd068d99e96efe2c \ + --hash=sha256:e282aedd63c639c07c3857097fc0e236f984ceb4089a8b284da1c526491e3f3d \ + --hash=sha256:e290d79a4107d7d794634ce3e985b9ae4f920380a813717adf61804904dc4393 \ + --hash=sha256:e3d9d13603410b72787579769469af730c38f2f25505573a5888a94b62b920f8 \ + --hash=sha256:e481bba1e11ba585fb06db666bfc23dbe181dbafc7b25776156120bf12e0d5a6 \ + --hash=sha256:e49b052b768bb74f58c7dda4e0bdf7b79d43a9204ca584ffe1fb48a6f3c84c66 \ + --hash=sha256:eb00b549b13bd6d884c863554566095bf6fa9c3cecb2e7b399c4bc7904cb33b5 \ + --hash=sha256:ec87c44f619380878bd49ca109669c9f221d9ae6883a5bcb3616785fa8f94c97 \ + --hash=sha256:edcfa83e03370032a489430215c1e7783128808fd3e2e0a3225deee278585196 \ + --hash=sha256:f11ae142f3a322d44513de1018b50f474f8f736bc3cd91d969f464b5bfef8836 \ + --hash=sha256:f2a09f6184f17a80897172863a655467da2b11151ec98ba8d7af89f17bf63dae \ + --hash=sha256:f5b65529bb2f21ac7861a0e94fdbf5dc0daab41497d18223b46ee8515e5ad297 \ + --hash=sha256:f60fdd125d85bf9c279ffb8e94c78c51b3b6a37711464e1f5f31078b45002421 \ + --hash=sha256:f61efaf4bed1cc0860e567d2ecb2363974d414f7f1f124b1df368bbf183453a6 \ + --hash=sha256:f90e552ecbad426eab352e7b2933091f2be77115bb16f09f78404861c8322981 \ + --hash=sha256:f956196ef61369f1685d14dad80611488d8dc1ef00be57c0c5a03064005b0f30 \ + --hash=sha256:fb91819461b1b56d06fa4bcf86617fac795f6a99d12239fb0c68dbeba41a0a30 \ + --hash=sha256:fbc9d316552f9ef7bba39f4edfad4a734d3d6f93341232a9dddadec4f15d425f \ + --hash=sha256:ff69a9a0b4b17d78170c73abe2ab12084bdf1691550c5629ad1fe7849433f324 \ + --hash=sha256:ffb2be176fed4457e445fe540617f0252a72a8bc56208fd65a690fdb1f57660b + # via -r requirements.in +markupsafe==2.1.5 \ + --hash=sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf \ + --hash=sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff \ + --hash=sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f \ + --hash=sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3 \ + --hash=sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532 \ + --hash=sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f \ + --hash=sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617 \ + --hash=sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df \ + --hash=sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4 \ + --hash=sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906 \ + --hash=sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f \ + --hash=sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4 \ + --hash=sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8 \ + --hash=sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371 \ + --hash=sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2 \ + --hash=sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465 \ + --hash=sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52 \ + --hash=sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6 \ + --hash=sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169 \ + --hash=sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad \ + --hash=sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2 \ + --hash=sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0 \ + --hash=sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029 \ + --hash=sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f \ + --hash=sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a \ + --hash=sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced \ + --hash=sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5 \ + --hash=sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c \ + --hash=sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf \ + --hash=sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9 \ + --hash=sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb \ + --hash=sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad \ + --hash=sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3 \ + --hash=sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1 \ + --hash=sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46 \ + --hash=sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc \ + --hash=sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a \ + --hash=sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee \ + --hash=sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900 \ + --hash=sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5 \ + --hash=sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea \ + --hash=sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f \ + --hash=sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5 \ + --hash=sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e \ + --hash=sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a \ + --hash=sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f \ + --hash=sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50 \ + --hash=sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a \ + --hash=sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b \ + --hash=sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4 \ + --hash=sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff \ + --hash=sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2 \ + --hash=sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46 \ + --hash=sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b \ + --hash=sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf \ + --hash=sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5 \ + --hash=sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5 \ + --hash=sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab \ + --hash=sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd \ + --hash=sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68 + # via + # -r requirements.in + # jinja2 +mypy-extensions==1.0.0 \ + --hash=sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d \ + --hash=sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782 + # via + # -r requirements.in + # black +nox==2024.4.15 \ + --hash=sha256:6492236efa15a460ecb98e7b67562a28b70da006ab0be164e8821177577c0565 \ + --hash=sha256:ecf6700199cdfa9e5ea0a41ff5e6ef4641d09508eda6edb89d9987864115817f + # via -r requirements.in +packaging==23.2 \ + --hash=sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5 \ + --hash=sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7 + # via + # -r requirements.in + # black + # nox +parameterized==0.9.0 \ + --hash=sha256:4e0758e3d41bea3bbd05ec14fc2c24736723f243b28d702081aef438c9372b1b \ + --hash=sha256:7fc905272cefa4f364c1a3429cbbe9c0f98b793988efb5bf90aac80f08db09b1 + # via -r requirements.in +pathspec==0.12.1 \ + --hash=sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08 \ + --hash=sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712 + # via + # -r requirements.in + # black +platformdirs==4.2.2 \ + --hash=sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee \ + --hash=sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3 + # via + # black + # virtualenv +pyyaml==6.0.1 \ + --hash=sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5 \ + --hash=sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc \ + --hash=sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df \ + --hash=sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741 \ + --hash=sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206 \ + --hash=sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27 \ + --hash=sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595 \ + --hash=sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62 \ + --hash=sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98 \ + --hash=sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696 \ + --hash=sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290 \ + --hash=sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9 \ + --hash=sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d \ + --hash=sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6 \ + --hash=sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867 \ + --hash=sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47 \ + --hash=sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486 \ + --hash=sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6 \ + --hash=sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3 \ + --hash=sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007 \ + --hash=sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938 \ + --hash=sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0 \ + --hash=sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c \ + --hash=sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735 \ + --hash=sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d \ + --hash=sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28 \ + --hash=sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4 \ + --hash=sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba \ + --hash=sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8 \ + --hash=sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef \ + --hash=sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5 \ + --hash=sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd \ + --hash=sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3 \ + --hash=sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0 \ + --hash=sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515 \ + --hash=sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c \ + --hash=sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c \ + --hash=sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924 \ + --hash=sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34 \ + --hash=sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43 \ + --hash=sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859 \ + --hash=sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673 \ + --hash=sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54 \ + --hash=sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a \ + --hash=sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b \ + --hash=sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab \ + --hash=sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa \ + --hash=sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c \ + --hash=sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585 \ + --hash=sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d \ + --hash=sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f + # via -r requirements.in +requests==2.32.3 \ + --hash=sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760 \ + --hash=sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6 + # via -r requirements.in +smmap==5.0.1 \ + --hash=sha256:dceeb6c0028fdb6734471eb07c0cd2aae706ccaecab45965ee83f11c8d3b1f62 \ + --hash=sha256:e6d8668fa5f93e706934a62d7b4db19c8d9eb8cf2adbb75ef1b675aa332b69da + # via + # -r requirements.in + # gitdb +typing==3.7.4.3 \ + --hash=sha256:1187fb9c82fd670d10aa07bbb6cfcfe4bdda42d6fab8d5134f04e8c4d0b71cc9 \ + --hash=sha256:283d868f5071ab9ad873e5e52268d611e851c870a2ba354193026f2dfb29d8b5 + # via -r requirements.in +typing-extensions==4.0.1 \ + --hash=sha256:4ca091dea149f945ec56afb48dae714f21e8692ef22a395223bcd328961b6a0e \ + --hash=sha256:7f001e5ac290a0c0401508864c7ec868be4e701886d5b573a9528ed3973d9d3b + # via -r requirements.in +urllib3==2.2.2 \ + --hash=sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472 \ + --hash=sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168 + # via requests +virtualenv==20.26.3 \ + --hash=sha256:4c43a2a236279d9ea36a0d76f98d84bd6ca94ac4e0f4a3b9d46d05e10fea542a \ + --hash=sha256:8cc4a31139e796e9a7de2cd5cf2489de1217193116a8fd42328f1bd65f434589 + # via nox +watchdog==4.0.1 \ + --hash=sha256:0144c0ea9997b92615af1d94afc0c217e07ce2c14912c7b1a5731776329fcfc7 \ + --hash=sha256:03e70d2df2258fb6cb0e95bbdbe06c16e608af94a3ffbd2b90c3f1e83eb10767 \ + --hash=sha256:093b23e6906a8b97051191a4a0c73a77ecc958121d42346274c6af6520dec175 \ + --hash=sha256:123587af84260c991dc5f62a6e7ef3d1c57dfddc99faacee508c71d287248459 \ + --hash=sha256:17e32f147d8bf9657e0922c0940bcde863b894cd871dbb694beb6704cfbd2fb5 \ + --hash=sha256:206afc3d964f9a233e6ad34618ec60b9837d0582b500b63687e34011e15bb429 \ + --hash=sha256:4107ac5ab936a63952dea2a46a734a23230aa2f6f9db1291bf171dac3ebd53c6 \ + --hash=sha256:4513ec234c68b14d4161440e07f995f231be21a09329051e67a2118a7a612d2d \ + --hash=sha256:611be3904f9843f0529c35a3ff3fd617449463cb4b73b1633950b3d97fa4bfb7 \ + --hash=sha256:62c613ad689ddcb11707f030e722fa929f322ef7e4f18f5335d2b73c61a85c28 \ + --hash=sha256:667f3c579e813fcbad1b784db7a1aaa96524bed53437e119f6a2f5de4db04235 \ + --hash=sha256:6e8c70d2cd745daec2a08734d9f63092b793ad97612470a0ee4cbb8f5f705c57 \ + --hash=sha256:7577b3c43e5909623149f76b099ac49a1a01ca4e167d1785c76eb52fa585745a \ + --hash=sha256:998d2be6976a0ee3a81fb8e2777900c28641fb5bfbd0c84717d89bca0addcdc5 \ + --hash=sha256:a3c2c317a8fb53e5b3d25790553796105501a235343f5d2bf23bb8649c2c8709 \ + --hash=sha256:ab998f567ebdf6b1da7dc1e5accfaa7c6992244629c0fdaef062f43249bd8dee \ + --hash=sha256:ac7041b385f04c047fcc2951dc001671dee1b7e0615cde772e84b01fbf68ee84 \ + --hash=sha256:bca36be5707e81b9e6ce3208d92d95540d4ca244c006b61511753583c81c70dd \ + --hash=sha256:c9904904b6564d4ee8a1ed820db76185a3c96e05560c776c79a6ce5ab71888ba \ + --hash=sha256:cad0bbd66cd59fc474b4a4376bc5ac3fc698723510cbb64091c2a793b18654db \ + --hash=sha256:d10a681c9a1d5a77e75c48a3b8e1a9f2ae2928eda463e8d33660437705659682 \ + --hash=sha256:d4925e4bf7b9bddd1c3de13c9b8a2cdb89a468f640e66fbfabaf735bd85b3e35 \ + --hash=sha256:d7b9f5f3299e8dd230880b6c55504a1f69cf1e4316275d1b215ebdd8187ec88d \ + --hash=sha256:da2dfdaa8006eb6a71051795856bedd97e5b03e57da96f98e375682c48850645 \ + --hash=sha256:dddba7ca1c807045323b6af4ff80f5ddc4d654c8bce8317dde1bd96b128ed253 \ + --hash=sha256:e7921319fe4430b11278d924ef66d4daa469fafb1da679a2e48c935fa27af193 \ + --hash=sha256:e93f451f2dfa433d97765ca2634628b789b49ba8b504fdde5837cdcf25fdb53b \ + --hash=sha256:eebaacf674fa25511e8867028d281e602ee6500045b57f43b08778082f7f8b44 \ + --hash=sha256:ef0107bbb6a55f5be727cfc2ef945d5676b97bffb8425650dadbb184be9f9a2b \ + --hash=sha256:f0de0f284248ab40188f23380b03b59126d1479cd59940f2a34f8852db710625 \ + --hash=sha256:f27279d060e2ab24c0aa98363ff906d2386aa6c4dc2f1a374655d4e02a6c5e5e \ + --hash=sha256:f8affdf3c0f0466e69f5b3917cdd042f89c8c63aebdb9f7c078996f607cdb0f5 + # via -r requirements.in + +# WARNING: The following packages were not pinned, but pip requires them to be +# pinned when the requirements file includes hashes and the requirement is not +# satisfied by a package already installed. Consider using the --allow-unsafe flag. +# setuptools diff --git a/library_generation/setup.py b/library_generation/setup.py index 240d9264b3..9f78194241 100755 --- a/library_generation/setup.py +++ b/library_generation/setup.py @@ -9,6 +9,7 @@ version="0.1", package_dir={ "library_generation": ".", + "synthtool": "owlbot/synthtool", }, package_data={ "library_generation": [ @@ -25,5 +26,6 @@ "owlbot/templates/poms/*.j2", "owlbot/templates/java_library/**/*", ], + "synthtool": ["owlbot/synthtool/**/*"], }, )