1"""
2This is a pseudo-public API for downstream libraries. We ask that downstream
3authors
4
51) Try to avoid using internals directly altogether, and failing that,
62) Use only functions exposed here (or in core.internals)
7
8"""
9
10from __future__ import annotations
11
12from typing import TYPE_CHECKING
13import warnings
14
15import numpy as np
16
17from pandas._libs.internals import BlockPlacement
18from pandas.errors import Pandas4Warning
19
20from pandas.core.dtypes.common import pandas_dtype
21from pandas.core.dtypes.dtypes import (
22 DatetimeTZDtype,
23 ExtensionDtype,
24 PeriodDtype,
25)
26
27from pandas.core.arrays import (
28 DatetimeArray,
29 TimedeltaArray,
30)
31from pandas.core.construction import extract_array
32from pandas.core.internals.blocks import (
33 DatetimeLikeBlock,
34 check_ndim,
35 ensure_block_shape,
36 extract_pandas_array,
37 get_block_type,
38 maybe_coerce_values,
39)
40
41if TYPE_CHECKING:
42 from pandas._typing import (
43 ArrayLike,
44 Dtype,
45 )
46
47 from pandas.core.internals.blocks import Block
48
49
50def _make_block(values: ArrayLike, placement: np.ndarray) -> Block:
51 """
52 This is an analogue to blocks.new_block(_2d) that ensures:
53 1) correct dimension for EAs that support 2D (`ensure_block_shape`), and
54 2) correct EA class for datetime64/timedelta64 (`maybe_coerce_values`).
55
56 The input `values` is assumed to be either numpy array or ExtensionArray:
57 - In case of a numpy array, it is assumed to already be in the expected
58 shape for Blocks (2D, (cols, rows)).
59 - In case of an ExtensionArray the input can be 1D, also for EAs that are
60 internally stored as 2D.
61
62 For the rest no preprocessing or validation is done, except for those dtypes
63 that are internally stored as EAs but have an exact numpy equivalent (and at
64 the moment use that numpy dtype), i.e. datetime64/timedelta64.
65 """
66 dtype = values.dtype
67 klass = get_block_type(dtype)
68 placement_obj = BlockPlacement(placement)
69
70 if (isinstance(dtype, ExtensionDtype) and dtype._supports_2d) or isinstance(
71 values, (DatetimeArray, TimedeltaArray)
72 ):
73 values = ensure_block_shape(values, ndim=2)
74
75 values = maybe_coerce_values(values)
76 return klass(values, ndim=2, placement=placement_obj)
77
78
79class _DatetimeTZBlock(DatetimeLikeBlock):
80 """implement a datetime64 block with a tz attribute"""
81
82 values: DatetimeArray
83
84 __slots__ = ()
85
86
87def make_block(
88 values: ArrayLike,
89 placement: BlockPlacement | np.ndarray,
90 klass: type[Block] | None = None,
91 ndim: int | None = None,
92 dtype: Dtype | None = None,
93) -> Block:
94 """
95 This is a pseudo-public analogue to blocks.new_block.
96
97 We ask that downstream libraries use this rather than any fully-internal
98 APIs, including but not limited to:
99
100 - core.internals.blocks.make_block
101 - Block.make_block
102 - Block.make_block_same_class
103 - Block.__init__
104 """
105 warnings.warn(
106 # GH#56815
107 "make_block is deprecated and will be removed in a future version. "
108 "Use pd.api.internals.create_dataframe_from_blocks or "
109 "(recommended) higher-level public APIs instead.",
110 Pandas4Warning,
111 stacklevel=2,
112 )
113
114 if dtype is not None:
115 dtype = pandas_dtype(dtype)
116
117 values, dtype = extract_pandas_array(values, dtype, ndim)
118
119 from pandas.core.internals.blocks import ExtensionBlock
120
121 if klass is ExtensionBlock and isinstance(values.dtype, PeriodDtype):
122 # GH-44681 changed PeriodArray to be stored in the 2D
123 # NDArrayBackedExtensionBlock instead of ExtensionBlock
124 # -> still allow ExtensionBlock to be passed in this case for back compat
125 klass = None
126
127 if klass is None:
128 dtype = dtype or values.dtype
129 klass = get_block_type(dtype)
130
131 elif klass is _DatetimeTZBlock and not isinstance(values.dtype, DatetimeTZDtype):
132 # pyarrow calls get here (pyarrow<15)
133 values = DatetimeArray._simple_new(
134 # error: Argument 1 to "_simple_new" of "DatetimeArray" has
135 # incompatible type "ExtensionArray | ndarray[tuple[Any, ...],
136 # dtype[Any]]"; expected
137 # "ndarray[tuple[Any, ...], dtype[datetime64[date | int | None]]]"
138 values, # type: ignore[arg-type]
139 # error: Argument "dtype" to "_simple_new" of "DatetimeArray" has
140 # incompatible type "Union[ExtensionDtype, dtype[Any], None]";
141 # expected "Union[dtype[datetime64], DatetimeTZDtype]"
142 dtype=dtype, # type: ignore[arg-type]
143 )
144
145 if not isinstance(placement, BlockPlacement):
146 placement = BlockPlacement(placement)
147
148 ndim = _maybe_infer_ndim(values, placement, ndim)
149 if isinstance(values.dtype, (PeriodDtype, DatetimeTZDtype)):
150 # GH#41168 ensure we can pass 1D dt64tz values
151 # More generally, any EA dtype that isn't is_1d_only_ea_dtype
152 values = extract_array(values, extract_numpy=True)
153 values = ensure_block_shape(values, ndim)
154
155 check_ndim(values, placement, ndim)
156 values = maybe_coerce_values(values)
157 return klass(values, ndim=ndim, placement=placement)
158
159
160def _maybe_infer_ndim(
161 values: ArrayLike, placement: BlockPlacement, ndim: int | None
162) -> int:
163 """
164 If `ndim` is not provided, infer it from placement and values.
165 """
166 if ndim is None:
167 # GH#38134 Block constructor now assumes ndim is not None
168 if not isinstance(values.dtype, np.dtype):
169 if len(placement) != 1:
170 ndim = 1
171 else:
172 ndim = 2
173 else:
174 ndim = values.ndim
175 return ndim
176
177
178def maybe_infer_ndim(
179 values: ArrayLike, placement: BlockPlacement, ndim: int | None
180) -> int:
181 """
182 If `ndim` is not provided, infer it from placement and values.
183 """
184 warnings.warn(
185 "maybe_infer_ndim is deprecated and will be removed in a future version.",
186 Pandas4Warning,
187 stacklevel=2,
188 )
189 return _maybe_infer_ndim(values, placement, ndim)