ronantakizawa/codereview-bench huggingface dataset:
Repository: apache/arrow
File: python/pyarrow/_compute_docstrings.py (Python)

Code before:

    This function also works with string values.

    >>> arr4 = pa.array(["z", None, "y", "x"])
    >>> pc.max(arr4)
    <pyarrow.StringScalar: 'z'>
    """

function_doc_additions["min_max"] = """
    Examples
    --------
    >>> import pyarrow as pa
    >>> import pyarrow.compute as pc
    >>> arr1 = pa.array([1, 1, 2, 2, 3, 2, 2, 2])
    >>> pc.min_max(arr1)
    <pyarrow.StructScalar: [('min', 1), ('max', 3)]>

    Using `skip_nulls` to handle null values.

    >>> arr2 = pa.array([1.0, None, 2.0, 3.0])
    >>> pc.min_max(arr2)
    <pyarrow.StructScalar: [('min', 1.0), ('max', 3.0)]>
    >>> pc.min_max(arr2, skip_nulls=False)
    <pyarrow.StructScalar: [('min', None), ('max', None)]>

    Using `ScalarAggregateOptions` to control minimum number of non-null values.

    >>> arr3 = pa.array([1.0, None, float("nan"), 3.0])
    >>> pc.min_max(arr3)
    <pyarrow.StructScalar: [('min', 1.0), ('max', 3.0)]>
    >>> pc.min_max(arr3, options=pc.ScalarAggregateOptions(min_count=3))
    <pyarrow.StructScalar: [('min', 1.0), ('max', 3.0)]>
    >>> pc.min_max(arr3, options=pc.ScalarAggregateOptions(min_count=4))
    <pyarrow.StructScalar: [('min', None), ('max', None)]>

    This function also works with string values.

    >>> arr4 = pa.array(["z", None, "y", "x"])
    >>> pc.min_max(arr4)
    <pyarrow.StructScalar: [('min', 'x'), ('max', 'z')]>
    """


Review comment (suggestion):
```suggestion
    Using ``ScalarAggregateOptions`` to control minimum number of non-null values.
```

Code after:

    This function also works with string values.

    >>> arr4 = pa.array(["z", None, "y", "x"])
    >>> pc.max(arr4)
    <pyarrow.StringScalar: 'z'>
    """

function_doc_additions["min_max"] = """
    Examples
    --------
    >>> import pyarrow as pa
    >>> import pyarrow.compute as pc
    >>> arr1 = pa.array([1, 1, 2, 2, 3, 2, 2, 2])
    >>> pc.min_max(arr1)
    <pyarrow.StructScalar: [('min', 1), ('max', 3)]>

    Using ``skip_nulls`` to handle null values.

    >>> arr2 = pa.array([1.0, None, 2.0, 3.0])
    >>> pc.min_max(arr2)
    <pyarrow.StructScalar: [('min', 1.0), ('max', 3.0)]>
    >>> pc.min_max(arr2, skip_nulls=False)
    <pyarrow.StructScalar: [('min', None), ('max', None)]>

    Using ``ScalarAggregateOptions`` to control minimum number of non-null values.

    >>> arr3 = pa.array([1.0, None, float("nan"), 3.0])
    >>> pc.min_max(arr3)
    <pyarrow.StructScalar: [('min', 1.0), ('max', 3.0)]>
    >>> pc.min_max(arr3, options=pc.ScalarAggregateOptions(min_count=3))
    <pyarrow.StructScalar: [('min', 1.0), ('max', 3.0)]>
    >>> pc.min_max(arr3, options=pc.ScalarAggregateOptions(min_count=4))
    <pyarrow.StructScalar: [('min', None), ('max', None)]>

    This function also works with string values.

    >>> arr4 = pa.array(["z", None, "y", "x"])
    >>> pc.min_max(arr4)
    <pyarrow.StructScalar: [('min', 'x'), ('max', 'z')]>
    """
Repository: apache/arrow
File: cpp/src/arrow/status_test.cc (C++)

Code before:
  ASSERT_EQ(st.code(), StatusCode::IOError);
  ASSERT_EQ(StripContext(st.message()), error_status.message());

  st = f(my_namespace::StatusLike{42});
  ASSERT_TRUE(st.ok());
  st = f(my_namespace::StatusLike{43});
  ASSERT_EQ(st.code(), StatusCode::UnknownError);
  ASSERT_EQ(StripContext(st.message()), "StatusLike: 43");
}

#ifdef ARROW_EXTRA_ERROR_CONTEXT
TEST(StatusTest, ToStringWithoutContextLines) {
  Status status = Status::IOError("base error");
  status.AddContextLine("file1.cc", 42, "expr");
  status.AddContextLine("file2.cc", 100, "expr");

  ASSERT_EQ(status.ToStringWithoutContextLines(), "IOError: base error");

  Status status2(StatusCode::Invalid,
                 "Error message\nThis line has: a colon but no digits");
  status2.AddContextLine("file.cc", 20, "expr");

  std::string result = status2.ToStringWithoutContextLines();
  ASSERT_EQ(result.find("file.cc:20"), std::string::npos);
  ASSERT_NE(result.find("This line has: a colon but no digits"), std::string::npos);
  ASSERT_NE(result.find("Error message"), std::string::npos);
}
#endif

}  // namespace arrow


Review comment (bug):
Could you check the exact expected message instead for easy to debug failure message? If we use `find()` and `std::string::npos`, we can't show the actual message in the failure log.

```suggestion
  ASSERT_EQ(status2.ToStringWithoutContextLines(),
            "Error message\nThis line has: a colon but no digits");
```

Code after:
  ASSERT_EQ(st.code(), StatusCode::IOError);
  ASSERT_EQ(StripContext(st.message()), error_status.message());

  st = f(my_namespace::StatusLike{42});
  ASSERT_TRUE(st.ok());
  st = f(my_namespace::StatusLike{43});
  ASSERT_EQ(st.code(), StatusCode::UnknownError);
  ASSERT_EQ(StripContext(st.message()), "StatusLike: 43");
}

#ifdef ARROW_EXTRA_ERROR_CONTEXT
TEST(StatusTest, ToStringWithoutContextLines) {
  Status status = Status::IOError("base error");
  status.AddContextLine("file1.cc", 42, "expr");
  status.AddContextLine("file2.cc", 100, "expr");

  ASSERT_EQ(status.ToStringWithoutContextLines(), "IOError: base error");

  Status status2(StatusCode::Invalid,
                 "Error message\nThis line has: a colon but no digits");
  status2.AddContextLine("file.cc", 20, "expr");

  ASSERT_EQ(status2.ToStringWithoutContextLines(),
            "Invalid: Error message\nThis line has: a colon but no digits");
}
#endif

}  // namespace arrow
Repository: apache/arrow
File: cpp/src/arrow/array/array_test.cc (C++)

Code before:
  DayTimeIntervalBuilder builder;
  std::vector<DayTimeIntervalType::DayMilliseconds> values{{1, 100}, {3, 200}, {5, 300}};
  std::vector<bool> is_valid{true, false, true};
  std::vector<uint8_t> is_valid_bytes{1, 0, 1};

  ASSERT_OK(builder.AppendValues(values));
  ASSERT_OK(builder.AppendValues(values, is_valid));
  ASSERT_OK(builder.AppendValues(values.data(), values.size(), is_valid_bytes.data()));

  ASSERT_OK_AND_ASSIGN(auto array, builder.Finish());
  ASSERT_OK(array->ValidateFull());
  ASSERT_EQ(array->null_count(), 2);
  ASSERT_EQ(array->length(), 9);

  const auto& day_time_array = checked_cast<const DayTimeIntervalArray&>(*array);
  ASSERT_EQ(day_time_array.GetValue(0), values[0]);
  ASSERT_TRUE(day_time_array.IsNull(4));
  ASSERT_TRUE(day_time_array.IsNull(7));
  ASSERT_EQ(day_time_array.GetValue(2), values[2]);
}

TEST_F(TestDayTimeIntervalBuilder, TestConstructors) {
  DayTimeIntervalBuilder builder1;
  ASSERT_EQ(builder1.type()->id(), Type::INTERVAL_DAY_TIME);

  MemoryPool* pool = default_memory_pool();
  DayTimeIntervalBuilder builder2(pool);
  ASSERT_EQ(builder2.type()->id(), Type::INTERVAL_DAY_TIME);

  auto type = day_time_interval();
  DayTimeIntervalBuilder builder3(type, pool);
  ASSERT_EQ(builder3.type()->id(), Type::INTERVAL_DAY_TIME);
  ASSERT_TRUE(builder3.type()->Equals(type));
}

}  // namespace arrow


Review comment (suggestion):
```suggestion
  auto pool = default_memory_pool();
```

Code after:
  DayTimeIntervalBuilder builder;
  std::vector<DayTimeIntervalType::DayMilliseconds> values{{1, 100}, {3, 200}, {5, 300}};
  std::vector<bool> is_valid{true, false, true};
  std::vector<uint8_t> is_valid_bytes{1, 0, 1};

  ASSERT_OK(builder.AppendValues(values));
  ASSERT_OK(builder.AppendValues(values, is_valid));
  ASSERT_OK(builder.AppendValues(values.data(), values.size(), is_valid_bytes.data()));

  ASSERT_OK_AND_ASSIGN(auto array, builder.Finish());
  ASSERT_OK(array->ValidateFull());
  ASSERT_EQ(array->null_count(), 2);
  ASSERT_EQ(array->length(), 9);

  const auto& day_time_array = checked_cast<const DayTimeIntervalArray&>(*array);
  ASSERT_EQ(day_time_array.GetValue(0), values[0]);
  ASSERT_TRUE(day_time_array.IsNull(4));
  ASSERT_TRUE(day_time_array.IsNull(7));
  ASSERT_EQ(day_time_array.GetValue(2), values[2]);
}

TEST_F(TestDayTimeIntervalBuilder, TestConstructors) {
  DayTimeIntervalBuilder builder1;
  ASSERT_EQ(builder1.type()->id(), Type::INTERVAL_DAY_TIME);

  auto pool = default_memory_pool();
  DayTimeIntervalBuilder builder2(pool);
  ASSERT_EQ(builder2.type()->id(), Type::INTERVAL_DAY_TIME);

  auto type = day_time_interval();
  DayTimeIntervalBuilder builder3(type, pool);
  ASSERT_EQ(builder3.type()->id(), Type::INTERVAL_DAY_TIME);
  ASSERT_TRUE(builder3.type()->Equals(type));
}

}  // namespace arrow
Repository: apache/arrow
File: python/pyarrow/src/arrow/python/helpers.cc (C++)

Code before:
bool PyFloat_IsNaN(PyObject* obj) {
  return PyFloat_Check(obj) && std::isnan(PyFloat_AsDouble(obj));
}

namespace {

// UUID module static data - lazily initialized on first use
// Uses a conditional initialization strategy: std::once_flag when the GIL is
// disabled, or a simple boolean flag when the GIL is enabled.
// See the Pandas static data section below and ARROW-10519 for more details.
#ifdef Py_GIL_DISABLED
static std::once_flag uuid_static_initialized;
#else
static bool uuid_static_initialized = false;
#endif
static PyObject* uuid_UUID = nullptr;

void GetUuidStaticSymbols() {
  OwnedRef uuid_module;

  // Import uuid module
  Status s = ImportModule("uuid", &uuid_module);
  if (!s.ok()) {
    return;
  }

#ifndef Py_GIL_DISABLED
  if (uuid_static_initialized) {
    return;
  }
#endif

  OwnedRef ref;
  if (ImportFromModule(uuid_module.obj(), "UUID", &ref).ok()) {
    uuid_UUID = ref.obj();
  }
}

#ifdef Py_GIL_DISABLED
void InitUuidStaticData() {
  std::call_once(uuid_static_initialized, GetUuidStaticSymbols);
}
#else
void InitUuidStaticData() {
  if (uuid_static_initialized) {
    return;
  }
  GetUuidStaticSymbols();
  uuid_static_initialized = true;
}
#endif

Review comment (suggestion):
```suggestion
#ifndef Py_GIL_DISABLED
  // Since ImportModule can release the GIL, another thread could have
  // already initialized the static data.
```

Code after:
bool PyFloat_IsNaN(PyObject* obj) {
  return PyFloat_Check(obj) && std::isnan(PyFloat_AsDouble(obj));
}

namespace {

// Thread-safe one-time Python module import + attribute lookup. For Pandas and UUID.
// Uses std::call_once when the GIL is disabled, or a simple boolean flag when
// the GIL is enabled to avoid deadlocks. See ARROW-10519 for more details and
// https://github.com/apache/arrow/commit/f69061935e92e36e25bb891177ca8bc4f463b272
struct ModuleOnceRunner {
  std::string module_name;
#ifdef Py_GIL_DISABLED
  std::once_flag initialized;
#else
  bool initialized = false;
#endif

  explicit ModuleOnceRunner(const std::string& module_name) : module_name(module_name) {}

  template <typename Func>
  void RunOnce(Func&& func) {
    auto do_init = [&]() {
      OwnedRef module;
      if (ImportModule(module_name, &module).ok()) {
#ifndef Py_GIL_DISABLED
        // Since ImportModule can release the GIL, another thread could have
        // already initialized the static data.
        if (initialized) {
          return;
        }
#endif
        func(module);
      }
    };
#ifdef Py_GIL_DISABLED
    std::call_once(initialized, do_init);
#else
    if (!initialized) {
      do_init();
      initialized = true;
    }
#endif
  }
};

static PyObject* uuid_UUID = nullptr;
static ModuleOnceRunner uuid_runner("uuid");

}  // namespace
Repository: apache/arrow
File: python/pyarrow/src/arrow/python/helpers.cc (C++)

Code before:
  OwnedRef uuid_module;

  // Import uuid module
  Status s = ImportModule("uuid", &uuid_module);
  if (!s.ok()) {
    return;
  }

#ifndef Py_GIL_DISABLED
  if (uuid_static_initialized) {
    return;
  }
#endif

  OwnedRef ref;
  if (ImportFromModule(uuid_module.obj(), "UUID", &ref).ok()) {
    uuid_UUID = ref.obj();
  }
}

#ifdef Py_GIL_DISABLED
void InitUuidStaticData() {
  std::call_once(uuid_static_initialized, GetUuidStaticSymbols);
}
#else
void InitUuidStaticData() {
  if (uuid_static_initialized) {
    return;
  }
  GetUuidStaticSymbols();
  uuid_static_initialized = true;
}
#endif

}  // namespace

bool IsPyUuid(PyObject* obj) {
  InitUuidStaticData();
  if (!uuid_UUID) return false;
  int result = PyObject_IsInstance(obj, uuid_UUID);
  if (result < 0) {
    PyErr_Clear();
    return false;
  }
  return result == 1;
}

namespace {

// This needs a conditional, because using std::once_flag could introduce
// a deadlock when the GIL is enabled. See

Review comment (bug):
```suggestion
void InitUuidStaticData() {
  // NOTE: This is called with the GIL held.  We needn't (and shouldn't,
  // to avoid deadlocks) use an additional C++ lock (ARROW-10519).
```

Code after:
  explicit ModuleOnceRunner(const std::string& module_name) : module_name(module_name) {}

  template <typename Func>
  void RunOnce(Func&& func) {
    auto do_init = [&]() {
      OwnedRef module;
      if (ImportModule(module_name, &module).ok()) {
#ifndef Py_GIL_DISABLED
        // Since ImportModule can release the GIL, another thread could have
        // already initialized the static data.
        if (initialized) {
          return;
        }
#endif
        func(module);
      }
    };
#ifdef Py_GIL_DISABLED
    std::call_once(initialized, do_init);
#else
    if (!initialized) {
      do_init();
      initialized = true;
    }
#endif
  }
};

static PyObject* uuid_UUID = nullptr;
static ModuleOnceRunner uuid_runner("uuid");

}  // namespace

bool IsPyUuid(PyObject* obj) {
  uuid_runner.RunOnce([](OwnedRef& module) {
    OwnedRef ref;
    if (ImportFromModule(module.obj(), "UUID", &ref).ok()) {
      uuid_UUID = ref.obj();
    }
  });
  if (!uuid_UUID) return false;
  int result = PyObject_IsInstance(obj, uuid_UUID);
  if (result < 0) {
    PyErr_Clear();
    return false;
  }
  return result != 0;
}

namespace {
Repository: apache/arrow
File: cpp/src/arrow/util/compression_zstd.cc (C++)

Code before:
// specific language governing permissions and limitations
// under the License.

#include "arrow/util/compression_internal.h"

#include <cstddef>
#include <cstdint>
#include <memory>
#include <unordered_map>

#include <zstd.h>

#include "arrow/result.h"
#include "arrow/status.h"
#include "arrow/util/logging_internal.h"
#include "arrow/util/macros.h"

using std::size_t;

namespace arrow {
namespace util {
namespace internal {

namespace {

using CCtxPtr = std::unique_ptr<ZSTD_CCtx, size_t (*)(ZSTD_CCtx*)>;
using DCtxPtr = std::unique_ptr<ZSTD_DCtx, size_t (*)(ZSTD_DCtx*)>;

Status ZSTDError(size_t ret, const char* prefix_msg) {
  return Status::IOError(prefix_msg, ZSTD_getErrorName(ret));
}

// ----------------------------------------------------------------------
// ZSTD decompressor implementation

class ZSTDDecompressor : public Decompressor {
 public:
  explicit ZSTDDecompressor(DCtxPtr stream) : stream_(std::move(stream)) {}

  Result<DecompressResult> Decompress(int64_t input_len, const uint8_t* input,
                                      int64_t output_len, uint8_t* output) override {
    ZSTD_inBuffer in_buf;
    ZSTD_outBuffer out_buf;

    in_buf.src = input;
    in_buf.size = static_cast<size_t>(input_len);
    in_buf.pos = 0;
    out_buf.dst = output;
    out_buf.size = static_cast<size_t>(output_len);
    out_buf.pos = 0;


Review comment (suggestion):
We can probably make this more readable
```suggestion
using CCtxPtr = std::unique_ptr<ZSTD_CCtx, decltype(&ZSTD_freeCCtx)>;
```

Code after:
// specific language governing permissions and limitations
// under the License.

#include "arrow/util/compression_internal.h"

#include <cstddef>
#include <cstdint>
#include <memory>
#include <utility>
#include <vector>

#include <zstd.h>

#include "arrow/result.h"
#include "arrow/status.h"
#include "arrow/util/logging_internal.h"
#include "arrow/util/macros.h"

using std::size_t;

namespace arrow {
namespace util {
namespace internal {

namespace {

using CCtxPtr = std::unique_ptr<ZSTD_CCtx, decltype(&ZSTD_freeCCtx)>;
using DCtxPtr = std::unique_ptr<ZSTD_DCtx, decltype(&ZSTD_freeDCtx)>;

Status ZSTDError(size_t ret, const char* prefix_msg) {
  return Status::IOError(prefix_msg, ZSTD_getErrorName(ret));
}

// ----------------------------------------------------------------------
// ZSTD decompressor implementation

class ZSTDDecompressor : public Decompressor {
 public:
  explicit ZSTDDecompressor(DCtxPtr stream) : stream_(std::move(stream)) {}

  Result<DecompressResult> Decompress(int64_t input_len, const uint8_t* input,
                                      int64_t output_len, uint8_t* output) override {
    ZSTD_inBuffer in_buf;
    ZSTD_outBuffer out_buf;

    in_buf.src = input;
    in_buf.size = static_cast<size_t>(input_len);
    in_buf.pos = 0;
    out_buf.dst = output;
    out_buf.size = static_cast<size_t>(output_len);
    out_buf.pos = 0;
Repository: apache/arrow
File: dev/release/05-binary-upload.sh (Shell)

Code before:
    cp -a "${target}" "${dist_dir}/${base_name}"
    # Skip signing/checksumming .sha512 files (e.g., R binaries already include checksums from CI)
    if [[ "${base_name}" != *.sha512 ]]; then
      gpg \
        --armor \
        --detach-sign \
        --local-user "${GPG_KEY_ID}" \
        --output "${dist_dir}/${base_name}.asc" \
        "${target}"
      pushd "${dist_dir}"
      shasum -a 512 "${base_name}" >"${base_name}.sha512"
      popd
    fi
  done
  gh release upload \
    --repo apache/arrow \
    "apache-arrow-${version}-rc${rc}" \
    "${dist_dir}"/*
}

if [ "${UPLOAD_DOCS}" -gt 0 ]; then
  upload_to_github_release docs "${ARROW_ARTIFACTS_DIR}"/*-docs/*
fi
if [ "${UPLOAD_ODBC}" -gt 0 ]; then
  upload_to_github_release odbc \
    "${ARROW_ARTIFACTS_DIR}"/"Apache-Arrow-Flight-SQL-ODBC"-*-win64.msi
fi
if [ "${UPLOAD_PYTHON}" -gt 0 ]; then
  upload_to_github_release python \
    "${ARROW_ARTIFACTS_DIR}"/{python-sdist,wheel-*}/*
fi
if [ "${UPLOAD_R}" -gt 0 ]; then
  upload_to_github_release r \
    "${ARROW_ARTIFACTS_DIR}"/r-binary-packages/r-lib*
fi

rake_tasks=()
apt_targets=()
yum_targets=()
if [ "${UPLOAD_ALMALINUX}" -gt 0 ]; then
  rake_tasks+=(yum:rc)
  yum_targets+=(almalinux)
fi
if [ "${UPLOAD_AMAZON_LINUX}" -gt 0 ]; then
  rake_tasks+=(yum:rc)
  yum_targets+=(amazon-linux)
fi
if [ "${UPLOAD_CENTOS}" -gt 0 ]; then
  rake_tasks+=(yum:rc)
  yum_targets+=(centos)
fi

Review comment (suggestion):
Let's remove needless quotes.

```suggestion
    "${ARROW_ARTIFACTS_DIR}"/Apache-Arrow-Flight-SQL-ODBC-*-win64.msi
```

Code after:
    cp -a "${target}" "${dist_dir}/${base_name}"
    # Skip signing/checksumming .sha512 files (e.g., R binaries already include checksums from CI)
    if [[ "${base_name}" != *.sha512 ]]; then
      gpg \
        --armor \
        --detach-sign \
        --local-user "${GPG_KEY_ID}" \
        --output "${dist_dir}/${base_name}.asc" \
        "${target}"
      pushd "${dist_dir}"
      shasum -a 512 "${base_name}" >"${base_name}.sha512"
      popd
    fi
  done
  gh release upload \
    --repo apache/arrow \
    "apache-arrow-${version}-rc${rc}" \
    "${dist_dir}"/*
}

if [ "${UPLOAD_DOCS}" -gt 0 ]; then
  upload_to_github_release docs "${ARROW_ARTIFACTS_DIR}"/*-docs/*
fi
if [ "${UPLOAD_ODBC}" -gt 0 ]; then
  upload_to_github_release odbc \
    "${ARROW_ARTIFACTS_DIR}"/Apache-Arrow-Flight-SQL-ODBC-*-win64.msi
fi
if [ "${UPLOAD_PYTHON}" -gt 0 ]; then
  upload_to_github_release python \
    "${ARROW_ARTIFACTS_DIR}"/{python-sdist,wheel-*}/*
fi
if [ "${UPLOAD_R}" -gt 0 ]; then
  upload_to_github_release r \
    "${ARROW_ARTIFACTS_DIR}"/r-binary-packages/r-lib*
fi

rake_tasks=()
apt_targets=()
yum_targets=()
if [ "${UPLOAD_ALMALINUX}" -gt 0 ]; then
  rake_tasks+=(yum:rc)
  yum_targets+=(almalinux)
fi
if [ "${UPLOAD_AMAZON_LINUX}" -gt 0 ]; then
  rake_tasks+=(yum:rc)
  yum_targets+=(amazon-linux)
fi
if [ "${UPLOAD_CENTOS}" -gt 0 ]; then
  rake_tasks+=(yum:rc)
  yum_targets+=(centos)
fi
Repository: apache/arrow
File: ci/scripts/python_wheel_validate_contents.py (Python)

Code before:


def validate_wheel(path):
    p = Path(path)
    wheels = list(p.glob('*.whl'))
    error_msg = f"{len(wheels)} wheels found but only 1 expected ({wheels})"
    assert len(wheels) == 1, error_msg
    f = zipfile.ZipFile(wheels[0])
    outliers = [
        info.filename for info in f.filelist if not re.match(
            r'(pyarrow/|pyarrow-[-.\w\d]+\.dist-info/|pyarrow\.libs/)', info.filename
        )
    ]
    assert not outliers, f"Unexpected contents in wheel: {sorted(outliers)}"
    license = [
        info.filename for info in f.filelist if re.search(
            r'LICENSE.txt$', info.filename
        )
    ]
    assert license, "LICENSE.txt is missing from the wheel."
    notice = [
        info.filename for info in f.filelist if re.search(
            r'NOTICE.txt$', info.filename
        )
    ]
    assert notice, "NOTICE.txt is missing from the wheel."
    print(f"The wheel: {wheels[0]} seems valid.")


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("--path", type=str, required=True,
                        help="Directory where wheel is located")
    args = parser.parse_args()
    validate_wheel(args.path)


if __name__ == '__main__':
    main()


Review comment (suggestion):
Nit:

```suggestion
  for filename in ('LICENSE.txt', 'NOTICE.txt'):
      assert any(info.filename.endswith(filename) for info in f.filelist), \
          f"{filename} is missing from the wheel."
```

Code after:


def validate_wheel(path):
    p = Path(path)
    wheels = list(p.glob('*.whl'))
    error_msg = f"{len(wheels)} wheels found but only 1 expected ({wheels})"
    assert len(wheels) == 1, error_msg
    f = zipfile.ZipFile(wheels[0])
    outliers = [
        info.filename for info in f.filelist if not re.match(
            r'(pyarrow/|pyarrow-[-.\w\d]+\.dist-info/|pyarrow\.libs/)', info.filename
        )
    ]
    assert not outliers, f"Unexpected contents in wheel: {sorted(outliers)}"
    for filename in ('LICENSE.txt', 'NOTICE.txt'):
        assert any(info.filename.split("/")[-1] == filename
                   for info in f.filelist), \
            f"{filename} is missing from the wheel."
    print(f"The wheel: {wheels[0]} seems valid.")


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("--path", type=str, required=True,
                        help="Directory where wheel is located")
    args = parser.parse_args()
    validate_wheel(args.path)


if __name__ == '__main__':
    main()