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, placement, klass=None, ndim=None, dtype: Dtype | None = None
89) -> Block:
90 """
91 This is a pseudo-public analogue to blocks.new_block.
92
93 We ask that downstream libraries use this rather than any fully-internal
94 APIs, including but not limited to:
95
96 - core.internals.blocks.make_block
97 - Block.make_block
98 - Block.make_block_same_class
99 - Block.__init__
100 """
101 warnings.warn(
102 # GH#56815
103 "make_block is deprecated and will be removed in a future version. "
104 "Use pd.api.internals.create_dataframe_from_blocks or "
105 "(recommended) higher-level public APIs instead.",
106 Pandas4Warning,
107 stacklevel=2,
108 )
109
110 if dtype is not None:
111 dtype = pandas_dtype(dtype)
112
113 values, dtype = extract_pandas_array(values, dtype, ndim)
114
115 from pandas.core.internals.blocks import ExtensionBlock
116
117 if klass is ExtensionBlock and isinstance(values.dtype, PeriodDtype):
118 # GH-44681 changed PeriodArray to be stored in the 2D
119 # NDArrayBackedExtensionBlock instead of ExtensionBlock
120 # -> still allow ExtensionBlock to be passed in this case for back compat
121 klass = None
122
123 if klass is None:
124 dtype = dtype or values.dtype
125 klass = get_block_type(dtype)
126
127 elif klass is _DatetimeTZBlock and not isinstance(values.dtype, DatetimeTZDtype):
128 # pyarrow calls get here (pyarrow<15)
129 values = DatetimeArray._simple_new(
130 # error: Argument "dtype" to "_simple_new" of "DatetimeArray" has
131 # incompatible type "Union[ExtensionDtype, dtype[Any], None]";
132 # expected "Union[dtype[datetime64], DatetimeTZDtype]"
133 values,
134 dtype=dtype, # type: ignore[arg-type]
135 )
136
137 if not isinstance(placement, BlockPlacement):
138 placement = BlockPlacement(placement)
139
140 ndim = _maybe_infer_ndim(values, placement, ndim)
141 if isinstance(values.dtype, (PeriodDtype, DatetimeTZDtype)):
142 # GH#41168 ensure we can pass 1D dt64tz values
143 # More generally, any EA dtype that isn't is_1d_only_ea_dtype
144 values = extract_array(values, extract_numpy=True)
145 values = ensure_block_shape(values, ndim)
146
147 check_ndim(values, placement, ndim)
148 values = maybe_coerce_values(values)
149 return klass(values, ndim=ndim, placement=placement)
150
151
152def _maybe_infer_ndim(values, placement: BlockPlacement, ndim: int | None) -> int:
153 """
154 If `ndim` is not provided, infer it from placement and values.
155 """
156 if ndim is None:
157 # GH#38134 Block constructor now assumes ndim is not None
158 if not isinstance(values.dtype, np.dtype):
159 if len(placement) != 1:
160 ndim = 1
161 else:
162 ndim = 2
163 else:
164 ndim = values.ndim
165 return ndim
166
167
168def maybe_infer_ndim(values, placement: BlockPlacement, ndim: int | None) -> int:
169 """
170 If `ndim` is not provided, infer it from placement and values.
171 """
172 warnings.warn(
173 "maybe_infer_ndim is deprecated and will be removed in a future version.",
174 Pandas4Warning,
175 stacklevel=2,
176 )
177 return _maybe_infer_ndim(values, placement, ndim)