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 41def normalize_name(name): 42 """normalize a PyPI package name and return a valid bazel label. 43 44 Args: 45 name: str, the PyPI package name. 46 47 Returns: 48 a normalized name as a string. 49 """ 50 name = name.replace("-", "_").replace(".", "_").lower() 51 if "__" not in name: 52 return name 53 54 # Handle the edge-case where there are consecutive `-`, `_` or `.` characters, 55 # which is a valid Python package name. 56 return "_".join([ 57 part 58 for part in name.split("_") 59 if part 60 ]) 61