From 382de32e8137a3a59a0800f46ef8a1de62b1a6e5 Mon Sep 17 00:00:00 2001 From: Vyas Ramasubramani Date: Mon, 3 Jun 2024 15:14:52 -0700 Subject: [PATCH] Add support for additional metaclasses of proxies and use for ExcelWriter (#15399) The ExcelWriter supports the abstract os.PathLike interface, but we would also like that support to be reflected in the class's MRO. Doing so is slightly complicated because os.PathLike is an ABC, and as such has a different metaclass. Therefore, in order to add os.PathLike as a base class, we must also generate a suitable combined metaclass for our ExcelWriter wrapper. This change ensures the `isinstance(pd.ExcelWriter(...), os.PathLike)` returns `True` when using cudf.pandas. Authors: - Vyas Ramasubramani (https://github.com/vyasr) - GALI PREM SAGAR (https://github.com/galipremsagar) Approvers: - GALI PREM SAGAR (https://github.com/galipremsagar) URL: https://github.com/rapidsai/cudf/pull/15399 --- python/cudf/cudf/pandas/_wrappers/pandas.py | 11 +++++-- python/cudf/cudf/pandas/fast_slow_proxy.py | 30 +++++++------------ .../cudf_pandas_tests/test_cudf_pandas.py | 5 ++++ 3 files changed, 25 insertions(+), 21 deletions(-) diff --git a/python/cudf/cudf/pandas/_wrappers/pandas.py b/python/cudf/cudf/pandas/_wrappers/pandas.py index 2e3880e14f6..698dd946022 100644 --- a/python/cudf/cudf/pandas/_wrappers/pandas.py +++ b/python/cudf/cudf/pandas/_wrappers/pandas.py @@ -1,8 +1,10 @@ # SPDX-FileCopyrightText: Copyright (c) 2023-2024, NVIDIA CORPORATION & AFFILIATES. # All rights reserved. # SPDX-License-Identifier: Apache-2.0 +import abc import copyreg import importlib +import os import pickle import sys @@ -857,7 +859,12 @@ def Index__new__(cls, *args, **kwargs): pd.ExcelWriter, fast_to_slow=_Unusable(), slow_to_fast=_Unusable(), - additional_attributes={"__hash__": _FastSlowAttribute("__hash__")}, + additional_attributes={ + "__hash__": _FastSlowAttribute("__hash__"), + "__fspath__": _FastSlowAttribute("__fspath__"), + }, + bases=(os.PathLike,), + metaclasses=(abc.ABCMeta,), ) try: @@ -1032,7 +1039,7 @@ def holiday_calendar_factory_wrapper(*args, **kwargs): fast_to_slow=_Unusable(), slow_to_fast=_Unusable(), additional_attributes={"__hash__": _FastSlowAttribute("__hash__")}, - meta_class=pd_HolidayCalendarMetaClass, + metaclasses=(pd_HolidayCalendarMetaClass,), ) Holiday = make_final_proxy_type( diff --git a/python/cudf/cudf/pandas/fast_slow_proxy.py b/python/cudf/cudf/pandas/fast_slow_proxy.py index 94caec1ce6c..169dd80e132 100644 --- a/python/cudf/cudf/pandas/fast_slow_proxy.py +++ b/python/cudf/cudf/pandas/fast_slow_proxy.py @@ -106,19 +106,6 @@ def __call__(self): _DELETE = object() -def create_composite_metaclass(base_meta, additional_meta): - """ - Dynamically creates a composite metaclass that inherits from both provided metaclasses. - This ensures that the metaclass behaviors of both base_meta and additional_meta are preserved. - """ - - class CompositeMeta(base_meta, additional_meta): - def __new__(cls, name, bases, namespace): - return super().__new__(cls, name, bases, namespace) - - return CompositeMeta - - def make_final_proxy_type( name: str, fast_type: type, @@ -130,7 +117,7 @@ def make_final_proxy_type( additional_attributes: Mapping[str, Any] | None = None, postprocess: Callable[[_FinalProxy, Any, Any], Any] | None = None, bases: Tuple = (), - meta_class=None, + metaclasses: Tuple = (), ) -> Type[_FinalProxy]: """ Defines a fast-slow proxy type for a pair of "final" fast and slow @@ -161,6 +148,8 @@ def make_final_proxy_type( construct said unwrapped object. See also `_maybe_wrap_result`. bases Optional tuple of base classes to insert into the mro. + metaclasses + Optional tuple of metaclasses to unify with the base proxy metaclass. Notes ----- @@ -241,15 +230,18 @@ def _fsproxy_state(self) -> _State: cls_dict[slow_name] = _FastSlowAttribute( slow_name, private=slow_name.startswith("_") ) - if meta_class is None: - meta_class = _FastSlowProxyMeta - else: - meta_class = create_composite_metaclass(_FastSlowProxyMeta, meta_class) + metaclass = _FastSlowProxyMeta + if metaclasses: + metaclass = types.new_class( # type: ignore + f"{name}_Meta", + metaclasses + (_FastSlowProxyMeta,), + {}, + ) cls = types.new_class( name, (*bases, _FinalProxy), - {"metaclass": meta_class}, + {"metaclass": metaclass}, lambda ns: ns.update(cls_dict), ) functools.update_wrapper( diff --git a/python/cudf/cudf_pandas_tests/test_cudf_pandas.py b/python/cudf/cudf_pandas_tests/test_cudf_pandas.py index 75bceea3034..fef829b17fc 100644 --- a/python/cudf/cudf_pandas_tests/test_cudf_pandas.py +++ b/python/cudf/cudf_pandas_tests/test_cudf_pandas.py @@ -6,6 +6,7 @@ import copy import datetime import operator +import os import pathlib import pickle import tempfile @@ -1421,3 +1422,7 @@ def test_holidays_within_dates(holiday, start, expected): utc.localize(xpd.Timestamp(start)), ) ) == [utc.localize(dt) for dt in expected] + + +def test_excelwriter_pathlike(): + assert isinstance(pd.ExcelWriter("foo.xlsx"), os.PathLike)