1# Copyright 2023 The Bazel Authors. 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""" 16Normalize a PyPI package name to allow consistent label names 17 18Note we chose `_` instead of `-` as a separator as there are certain 19requirements around Bazel labels that we need to consider. 20 21From the Bazel docs: 22> Package names must be composed entirely of characters drawn from the set 23> A-Z, a–z, 0–9, '/', '-', '.', and '_', and cannot start with a slash. 24 25However, due to restrictions on Bazel labels we also cannot allow hyphens. 26See https://github.com/bazelbuild/bazel/issues/6841 27 28Further, rules_python automatically adds the repository root to the 29PYTHONPATH, meaning a package that has the same name as a module is picked 30up. We workaround this by prefixing with `<hub_name>_`. 31 32Alternatively we could require 33`--noexperimental_python_import_all_repositories` be set, however this 34breaks rules_docker. 35See: https://github.com/bazelbuild/bazel/issues/2636 36 37Also see Python spec on normalizing package names: 38https://packaging.python.org/en/latest/specifications/name-normalization/ 39""" 40 41# Keep in sync with ../pip_install/tools/lib/bazel.py 42def normalize_name(name): 43 """normalize a PyPI package name and return a valid bazel label. 44 45 Args: 46 name: str, the PyPI package name. 47 48 Returns: 49 a normalized name as a string. 50 """ 51 name = name.replace("-", "_").replace(".", "_").lower() 52 if "__" not in name: 53 return name 54 55 # Handle the edge-case where there are consecutive `-`, `_` or `.` characters, 56 # which is a valid Python package name. 57 return "_".join([ 58 part 59 for part in name.split("_") 60 if part 61 ]) 62