# Version 1

## Breaking changes

### Properly apply `strict` parameter in Series constructor

The behavior of the Series constructor has been updated. Generally, it will be more strict, unless
the user passes `strict=False`.

Strict construction is more efficient than non-strict construction, so make sure to pass values of
the same data type to the constructor for the best performance.

**Example**

Before:

```pycon
>>> s = pl.Series([1, 2, 3.5])
shape: (3,)
Series: '' [f64]
[
        1.0
        2.0
        3.5
]
>>> s = pl.Series([1, 2, 3.5], strict=False)
shape: (3,)
Series: '' [i64]
[
        1
        2
        null
]
>>> s = pl.Series([1, 2, 3.5], strict=False, dtype=pl.Int8)
Series: '' [i8]
[
        1
        2
        null
]
```

After:

```pycon
>>> s = pl.Series([1, 2, 3.5])
Traceback (most recent call last):
...
TypeError: unexpected value while building Series of type Int64; found value of type Float64: 3.5

Hint: Try setting `strict=False` to allow passing data with mixed types.
>>> s = pl.Series([1, 2, 3.5], strict=False)
shape: (3,)
Series: '' [f64]
[
        1.0
        2.0
        3.5
]
>>> s = pl.Series([1, 2, 3.5], strict=False, dtype=pl.Int8)
Series: '' [i8]
[
        1
        2
        3
]
```

### Change data orientation inference logic for DataFrame construction

Polars no longer inspects data types to infer the orientation of the data passed to the DataFrame
constructor. Data orientation is inferred based on the data and schema dimensions.

Additionally, a warning is raised whenever row orientation is inferred. Because of some confusing
edge cases, users should pass `orient="row"` to make explicit that their input is row-based.

**Example**

Before:

```pycon
>>> data = [[1, "a"], [2, "b"]]
>>> pl.DataFrame(data)
shape: (2, 2)
┌──────────┬──────────┐
│ column_0 ┆ column_1 │
│ ---      ┆ ---      │
│ i64      ┆ str      │
╞══════════╪══════════╡
│ 1        ┆ a        │
│ 2        ┆ b        │
└──────────┴──────────┘
```

After:

```pycon
>>> pl.DataFrame(data)
Traceback (most recent call last):
...
TypeError: unexpected value while building Series of type Int64; found value of type String: "a"

Hint: Try setting `strict=False` to allow passing data with mixed types.
```

Use instead:

```pycon
>>> pl.DataFrame(data, orient="row")
shape: (2, 2)
┌──────────┬──────────┐
│ column_0 ┆ column_1 │
│ ---      ┆ ---      │
│ i64      ┆ str      │
╞══════════╪══════════╡
│ 1        ┆ a        │
│ 2        ┆ b        │
└──────────┴──────────┘
```

### Consistently convert to given time zone in Series constructor

!!! danger

    This change may silently impact the results of your pipelines.
    If you work with time zones, please make sure to account for this change.

Handling of time zone information in the Series and DataFrame constructors was inconsistent.
Row-wise construction would convert to the given time zone, while column-wise construction would
_replace_ the time zone. The inconsistency has been fixed by always converting to the time zone
specified in the data type.

**Example**

Before:

```pycon
>>> from datetime import datetime
>>> pl.Series([datetime(2020, 1, 1)], dtype=pl.Datetime('us', 'Europe/Amsterdam'))
shape: (1,)
Series: '' [datetime[μs, Europe/Amsterdam]]
[
        2020-01-01 00:00:00 CET
]
```

After:

```pycon
>>> from datetime import datetime
>>> pl.Series([datetime(2020, 1, 1)], dtype=pl.Datetime('us', 'Europe/Amsterdam'))
shape: (1,)
Series: '' [datetime[μs, Europe/Amsterdam]]
[
        2020-01-01 01:00:00 CET
]
```

### Update some error types to more appropriate variants

We have updated a lot of error types to more accurately represent the problem. Most commonly,
`ComputeError` types were changed to `InvalidOperationError` or `SchemaError`.

**Example**

Before:

```pycon
>>> s = pl.Series("a", [100, 200, 300])
>>> s.cast(pl.UInt8)
Traceback (most recent call last):
...
polars.exceptions.ComputeError: conversion from `i64` to `u8` failed in column 'a' for 1 out of 3 values: [300]
```

After:

```pycon
>>> s.cast(pl.UInt8)
Traceback (most recent call last):
...
polars.exceptions.InvalidOperationError: conversion from `i64` to `u8` failed in column 'a' for 1 out of 3 values: [300]
```

### Update `read/scan_parquet` to disable Hive partitioning by default for file inputs

Parquet reading functions now also support directory inputs. Hive partitioning is enabled by default
for directories, but is now _disabled_ by default for file inputs. File inputs include single files,
globs, and lists of files. Explicitly pass `hive_partitioning=True` to restore previous behavior.

**Example**

Before:

```pycon
>>> pl.read_parquet("dataset/a=1/foo.parquet")
shape: (2, 2)
┌─────┬─────┐
│ a   ┆ x   │
│ --- ┆ --- │
│ i64 ┆ f64 │
╞═════╪═════╡
│ 1   ┆ 1.0 │
│ 1   ┆ 2.0 │
└─────┴─────┘
```

After:

```pycon
>>> pl.read_parquet("dataset/a=1/foo.parquet")
shape: (2, 1)
┌─────┐
│ x   │
│ --- │
│ f64 │
╞═════╡
│ 1.0 │
│ 2.0 │
└─────┘
>>> pl.read_parquet("dataset/a=1/foo.parquet", hive_partitioning=True)
shape: (2, 2)
┌─────┬─────┐
│ a   ┆ x   │
│ --- ┆ --- │
│ i64 ┆ f64 │
╞═════╪═════╡
│ 1   ┆ 1.0 │
│ 1   ┆ 2.0 │
└─────┴─────┘
```

### Update `reshape` to return Array types instead of List types

`reshape` now returns an Array type instead of a List type.

Users can restore the old functionality by calling `.arr.to_list()` on the output. Note that this is
not more expensive than it would be to create a List type directly, because reshaping into an array
is basically free.

**Example**

Before:

```pycon
>>> s = pl.Series([1, 2, 3, 4, 5, 6])
>>> s.reshape((2, 3))
shape: (2,)
Series: '' [list[i64]]
[
        [1, 2, 3]
        [4, 5, 6]
]
```

After:

```pycon
>>> s.reshape((2, 3))
shape: (2,)
Series: '' [array[i64, 3]]
[
        [1, 2, 3]
        [4, 5, 6]
]
```

### Read 2D NumPy arrays as `Array` type instead of `List`

The Series constructor now parses 2D NumPy arrays as an `Array` type rather than a `List` type.

**Example**

Before:

```pycon
>>> import numpy as np
>>> arr = np.array([[1, 2], [3, 4]])
>>> pl.Series(arr)
shape: (2,)
Series: '' [list[i64]]
[
        [1, 2]
        [3, 4]
]
```

After:

```pycon
>>> import numpy as np
>>> arr = np.array([[1, 2], [3, 4]])
>>> pl.Series(arr)
shape: (2,)
Series: '' [array[i64, 2]]
[
        [1, 2]
        [3, 4]
]
```

### Split `replace` functionality into two separate methods

The API for `replace` has proven to be confusing to many users, particularly with regards to the
`default` argument and the resulting data type.

It has been split up into two methods: `replace` and `replace_strict`. `replace` now always keeps
the existing data type _(breaking, see example below)_ and is meant for replacing some values in
your existing column. Its parameters `default` and `return_dtype` have been deprecated.

The new method `replace_strict` is meant for creating a new column, mapping some or all of the
values of the original column, and optionally specifying a default value. If no default is provided,
it raises an error if any non-null values are not mapped.

**Example**

Before:

```pycon
>>> s = pl.Series([1, 2, 3])
>>> s.replace(1, "a")
shape: (3,)
Series: '' [str]
[
        "a"
        "2"
        "3"
]
```

After:

```pycon
>>> s.replace(1, "a")
Traceback (most recent call last):
...
polars.exceptions.InvalidOperationError: conversion from `str` to `i64` failed in column 'literal' for 1 out of 1 values: ["a"]
>>> s.replace_strict(1, "a", default=s)
shape: (3,)
Series: '' [str]
[
        "a"
        "2"
        "3"
]
```

### Preserve nulls in `ewm_mean`, `ewm_std`, and `ewm_var`

Polars will no longer forward-fill null values in `ewm` methods. The user can call `.forward_fill()`
on the output to achieve the same result.

**Example**

Before:

```pycon
>>> s = pl.Series([1, 4, None, 3])
>>> s.ewm_mean(alpha=.9, ignore_nulls=False)
shape: (4,)
Series: '' [f64]
[
        1.0
        3.727273
        3.727273
        3.007913
]
```

After:

```pycon
>>> s.ewm_mean(alpha=.9, ignore_nulls=False)
shape: (4,)
Series: '' [f64]
[
        1.0
        3.727273
        null
        3.007913
]
```

### Update `clip` to no longer propagate nulls in the given bounds

Null values in the bounds no longer set the value to null - instead, the original value is retained.

**Before**

```pycon
>>> df = pl.DataFrame({"a": [0, 1, 2], "min": [1, None, 1]})
>>> df.select(pl.col("a").clip("min"))
shape: (3, 1)
┌──────┐
│ a    │
│ ---  │
│ i64  │
╞══════╡
│ 1    │
│ null │
│ 2    │
└──────┘
```

**After**

```pycon
>>> df.select(pl.col("a").clip("min"))
shape: (3, 1)
┌──────┐
│ a    │
│ ---  │
│ i64  │
╞══════╡
│ 1    │
│ 1    │
│ 2    │
└──────┘
```

### Change `str.to_datetime` to default to microsecond precision for format specifiers `"%f"` and `"%.f"`

In `.str.to_datetime`, when specifying `%.f` as the format, the default was to set the resulting
datatype to nanosecond precision. This has been changed to microsecond precision.

#### Example

**Before**

```pycon
>>> s = pl.Series(["2022-08-31 00:00:00.123456789"])
>>> s.str.to_datetime(format="%Y-%m-%d %H:%M:%S%.f")
shape: (1,)
Series: '' [datetime[ns]]
[
        2022-08-31 00:00:00.123456789
]
```

**After**

```pycon
>>> s.str.to_datetime(format="%Y-%m-%d %H:%M:%S%.f")
shape: (1,)
Series: '' [datetime[us]]
[
        2022-08-31 00:00:00.123456
]
```

### Update resulting column names in `pivot` when pivoting by multiple values

In `DataFrame.pivot`, when specifying multiple `values` columns, the result would redundantly
include the `column` column in the column names. This has been addressed.

**Example**

Before:

```python
>>> df = pl.DataFrame(
...     {
...         "name": ["Cady", "Cady", "Karen", "Karen"],
...         "subject": ["maths", "physics", "maths", "physics"],
...         "test_1": [98, 99, 61, 58],
...         "test_2": [100, 100, 60, 60],
...     }
... )
>>> df.pivot(index='name', columns='subject', values=['test_1', 'test_2'])
shape: (2, 5)
┌───────┬──────────────────────┬────────────────────────┬──────────────────────┬────────────────────────┐
│ name  ┆ test_1_subject_maths ┆ test_1_subject_physics ┆ test_2_subject_maths ┆ test_2_subject_physics │
│ ---   ┆ ---                  ┆ ---                    ┆ ---                  ┆ ---                    │
│ str   ┆ i64                  ┆ i64                    ┆ i64                  ┆ i64                    │
╞═══════╪══════════════════════╪════════════════════════╪══════════════════════╪════════════════════════╡
│ Cady  ┆ 98                   ┆ 99                     ┆ 100                  ┆ 100                    │
│ Karen ┆ 61                   ┆ 58                     ┆ 60                   ┆ 60                     │
└───────┴──────────────────────┴────────────────────────┴──────────────────────┴────────────────────────┘
```

After:

```python
>>> df = pl.DataFrame(
...     {
...         "name": ["Cady", "Cady", "Karen", "Karen"],
...         "subject": ["maths", "physics", "maths", "physics"],
...         "test_1": [98, 99, 61, 58],
...         "test_2": [100, 100, 60, 60],
...     }
... )
>>> df.pivot('subject', index='name')
┌───────┬──────────────┬────────────────┬──────────────┬────────────────┐
│ name  ┆ test_1_maths ┆ test_1_physics ┆ test_2_maths ┆ test_2_physics │
│ ---   ┆ ---          ┆ ---            ┆ ---          ┆ ---            │
│ str   ┆ i64          ┆ i64            ┆ i64          ┆ i64            │
╞═══════╪══════════════╪════════════════╪══════════════╪════════════════╡
│ Cady  ┆ 98           ┆ 99             ┆ 100          ┆ 100            │
│ Karen ┆ 61           ┆ 58             ┆ 60           ┆ 60             │
└───────┴──────────────┴────────────────┴──────────────┴────────────────┘
```

Note that the function signature has also changed:

- `columns` has been renamed to `on`, and is now the first positional argument.
- `index` and `values` are both optional. If `index` is not specified, then it will use all columns
  not specified in `on` and `values`. If `values` is not specified, it will use all columns not
  specified in `on` and `index`.

### Support Decimal types by default when converting from Arrow

Update conversion from Arrow to always convert Decimals into Polars Decimals, rather than cast to
Float64. `Config.activate_decimals` has been removed.

**Example**

Before:

```pycon
>>> from decimal import Decimal as D
>>> import pyarrow as pa
>>> arr = pa.array([D("1.01"), D("2.25")])
>>> pl.from_arrow(arr)
shape: (2,)
Series: '' [f64]
[
        1.01
        2.25
]
```

After:

```pycon
>>> pl.from_arrow(arr)
shape: (2,)
Series: '' [decimal[3,2]]
[
        1.01
        2.25
]
```

### Remove serde functionality from `pl.read_json` and `DataFrame.write_json`

`pl.read_json` no longer supports reading JSON files produced by `DataFrame.serialize`. Users should
use `pl.DataFrame.deserialize` instead.

`DataFrame.write_json` now only writes row-oriented JSON. The parameters `row_oriented` and `pretty`
have been removed. Users should use `DataFrame.serialize` to serialize a DataFrame.

**Example - `write_json`**

Before:

```pycon
>>> df = pl.DataFrame({"a": [1, 2], "b": [3.0, 4.0]})
>>> df.write_json()
'{"columns":[{"name":"a","datatype":"Int64","bit_settings":"","values":[1,2]},{"name":"b","datatype":"Float64","bit_settings":"","values":[3.0,4.0]}]}'
```

After:

```pycon
>>> df.write_json()  # Same behavior as previously `df.write_json(row_oriented=True)`
'[{"a":1,"b":3.0},{"a":2,"b":4.0}]'
```

**Example - `read_json`**

Before:

```pycon
>>> import io
>>> df_ser = '{"columns":[{"name":"a","datatype":"Int64","bit_settings":"","values":[1,2]},{"name":"b","datatype":"Float64","bit_settings":"","values":[3.0,4.0]}]}'
>>> pl.read_json(io.StringIO(df_ser))
shape: (2, 2)
┌─────┬─────┐
│ a   ┆ b   │
│ --- ┆ --- │
│ i64 ┆ f64 │
╞═════╪═════╡
│ 1   ┆ 3.0 │
│ 2   ┆ 4.0 │
└─────┴─────┘
```

After:

```pycon
>>> pl.read_json(io.StringIO(df_ser))  # Format no longer supported: data is treated as a single row
shape: (1, 1)
┌─────────────────────────────────┐
│ columns                         │
│ ---                             │
│ list[struct[4]]                 │
╞═════════════════════════════════╡
│ [{"a","Int64","",[1.0, 2.0]}, … │
└─────────────────────────────────┘
```

Use instead:

```pycon
>>> pl.DataFrame.deserialize(io.StringIO(df_ser))
shape: (2, 2)
┌─────┬─────┐
│ a   ┆ b   │
│ --- ┆ --- │
│ i64 ┆ f64 │
╞═════╪═════╡
│ 1   ┆ 3.0 │
│ 2   ┆ 4.0 │
└─────┴─────┘
```

### `Series.equals` no longer checks names by default

Previously, `Series.equals` would return `False` if the Series names didn't match. The method now no
longer checks the names by default. The previous behavior can be retained by setting
`check_names=True`.

**Example**

Before:

```pycon
>>> s1 = pl.Series("foo", [1, 2, 3])
>>> s2 = pl.Series("bar", [1, 2, 3])
>>> s1.equals(s2)
False
```

After:

```pycon
>>> s1.equals(s2)
True
>>> s1.equals(s2, check_names=True)
False
```

### Remove `columns` parameter from `nth` expression function

The `columns` parameter was removed in favor of treating positional inputs as additional indices.
Use `Expr.get` instead to get the same functionality.

**Example**

Before:

```pycon
>>> df = pl.DataFrame({"a": [1, 2], "b": [3, 4], "c": [5, 6]})
>>> df.select(pl.nth(1, "a"))
shape: (1, 1)
┌─────┐
│ a   │
│ --- │
│ i64 │
╞═════╡
│ 2   │
└─────┘
```

After:

```pycon
>>> df.select(pl.nth(1, "a"))
...
TypeError: argument 'indices': 'str' object cannot be interpreted as an integer
```

Use instead:

```pycon
>>> df.select(pl.col("a").get(1))
shape: (1, 1)
┌─────┐
│ a   │
│ --- │
│ i64 │
╞═════╡
│ 2   │
└─────┘
```

### Rename struct fields of `rle` output

The struct fields of the `rle` method have been renamed from `lengths/values` to `len/value`. The
data type of the `len` field has also been updated to match the index type (was previously `Int32`,
now `UInt32`).

**Before**

```pycon
>>> s = pl.Series(["a", "a", "b", "c", "c", "c"])
>>> s.rle().struct.unnest()
shape: (3, 2)
┌─────────┬────────┐
│ lengths ┆ values │
│ ---     ┆ ---    │
│ i32     ┆ str    │
╞═════════╪════════╡
│ 2       ┆ a      │
│ 1       ┆ b      │
│ 3       ┆ c      │
└─────────┴────────┘
```

**After**

```pycon
>>> s.rle().struct.unnest()
shape: (3, 2)
┌─────┬───────┐
│ len ┆ value │
│ --- ┆ ---   │
│ u32 ┆ str   │
╞═════╪═══════╡
│ 2   ┆ a     │
│ 1   ┆ b     │
│ 3   ┆ c     │
└─────┴───────┘
```

### Update `set_sorted` to only accept a single column

Calling `set_sorted` indicates that a column is sorted _individually_. Passing multiple columns
indicates that each of those columns are also sorted individually. However, many users assumed this
meant that the columns were sorted as a group, which led to incorrect results.

To help users avoid this pitfall, we removed the possibility to specify multiple columns in
`set_sorted`. To set multiple columns as sorted, simply call `set_sorted` multiple times.

**Example**

Before:

```pycon
>>> df = pl.DataFrame({"a": [1, 2, 3], "b": [4.0, 5.0, 6.0], "c": [9, 7, 8]})
>>> df.set_sorted("a", "b")
```

After:

```pycon
>>> df.set_sorted("a", "b")
Traceback (most recent call last):
...
TypeError: DataFrame.set_sorted() takes 2 positional arguments but 3 were given
```

Use instead:

```pycon
>>> df.set_sorted("a").set_sorted("b")
```

### Default to raising on out-of-bounds indices in all `get`/`gather` operations

The default behavior was inconsistent between `get` and `gather` operations in various places. Now
all such operations will raise by default. Pass `null_on_oob=True` to restore previous behavior.

**Example**

Before:

```pycon
>>> s = pl.Series([[0, 1, 2], [0]])
>>> s.list.get(1)
shape: (2,)
Series: '' [i64]
[
        1
        null
]
```

After:

```pycon
>>> s.list.get(1)
Traceback (most recent call last):
...
polars.exceptions.ComputeError: get index is out of bounds
```

Use instead:

```pycon
>>> s.list.get(1, null_on_oob=True)
shape: (2,)
Series: '' [i64]
[
        1
        null
]
```

### Change default engine for `read_excel` to `"calamine"`

The `calamine` engine (available through the `fastexcel` package) has been added to Polars
relatively recently. It's much faster than the other engines, and was already the default for `xlsb`
and `xls` files. We now made it the default for all Excel files.

There may be subtle differences between this engine and the previous default (`xlsx2csv`). One clear
difference is that the `calamine` engine does not support the `engine_options` parameter. If you
cannot get your desired behavior with the `calamine` engine, specify `engine="xlsx2csv"` to restore
previous behavior.

### Example

Before:

```pycon
>>> pl.read_excel("data.xlsx", engine_options={"skip_empty_lines": True})
```

After:

```pycon
>>> pl.read_excel("data.xlsx", engine_options={"skip_empty_lines": True})
Traceback (most recent call last):
...
TypeError: read_excel() got an unexpected keyword argument 'skip_empty_lines'
```

Instead, explicitly specify the `xlsx2csv` engine or omit the `engine_options`:

```pycon
>>> pl.read_excel("data.xlsx", engine="xlsx2csv", engine_options={"skip_empty_lines": True})
```

### Remove class variables from some DataTypes

Some DataType classes had class variables. The `Datetime` class, for example, had `time_unit` and
`time_zone` as class variables. This was unintended: these should have been instance variables. This
has now been corrected.

**Example**

Before:

```pycon
>>> dtype = pl.Datetime
>>> dtype.time_unit is None
True
```

After:

```pycon
>>> dtype.time_unit is None
Traceback (most recent call last):
...
AttributeError: type object 'Datetime' has no attribute 'time_unit'
```

Use instead:

```pycon
>>> getattr(dtype, "time_unit", None) is None
True
```

### Change default `offset` in `group_by_dynamic` from 'negative `every`' to 'zero'

This affects the start of the first window in `group_by_dynamic`. The new behavior should align more
with user expectations.

**Example**

Before:

```pycon
>>> from datetime import date
>>> df = pl.DataFrame({
...     "ts": [date(2020, 1, 1), date(2020, 1, 2), date(2020, 1, 3)],
...     "value": [1, 2, 3],
... })
>>> df.group_by_dynamic("ts", every="1d", period="2d").agg("value")
shape: (4, 2)
┌────────────┬───────────┐
│ ts         ┆ value     │
│ ---        ┆ ---       │
│ date       ┆ list[i64] │
╞════════════╪═══════════╡
│ 2019-12-31 ┆ [1]       │
│ 2020-01-01 ┆ [1, 2]    │
│ 2020-01-02 ┆ [2, 3]    │
│ 2020-01-03 ┆ [3]       │
└────────────┴───────────┘
```

After:

```pycon
>>> df.group_by_dynamic("ts", every="1d", period="2d").agg("value")
shape: (3, 2)
┌────────────┬───────────┐
│ ts         ┆ value     │
│ ---        ┆ ---       │
│ date       ┆ list[i64] │
╞════════════╪═══════════╡
│ 2020-01-01 ┆ [1, 2]    │
│ 2020-01-02 ┆ [2, 3]    │
│ 2020-01-03 ┆ [3]       │
└────────────┴───────────┘
```

### Change default serialization format of `LazyFrame/DataFrame/Expr`

The only serialization format available for the `serialize/deserialize` methods on Polars objects
was JSON. We added a more optimized binary format and made this the default. JSON serialization is
still available by passing `format="json"`.

**Example**

Before:

```pycon
>>> lf = pl.LazyFrame({"a": [1, 2, 3]}).sum()
>>> serialized = lf.serialize()
>>> serialized
'{"MapFunction":{"input":{"DataFrameScan":{"df":{"columns":[{"name":...'
>>> from io import StringIO
>>> pl.LazyFrame.deserialize(StringIO(serialized)).collect()
shape: (1, 1)
┌─────┐
│ a   │
│ --- │
│ i64 │
╞═════╡
│ 6   │
└─────┘
```

After:

```pycon
>>> lf = pl.LazyFrame({"a": [1, 2, 3]}).sum()
>>> serialized = lf.serialize()
>>> serialized
b'\xa1kMapFunction\xa2einput\xa1mDataFrameScan\xa4bdf...'
>>> from io import BytesIO  # Note: using BytesIO instead of StringIO
>>> pl.LazyFrame.deserialize(BytesIO(serialized)).collect()
shape: (1, 1)
┌─────┐
│ a   │
│ --- │
│ i64 │
╞═════╡
│ 6   │
└─────┘
```

### Constrain access to globals from `DataFrame.sql` in favor of `pl.sql`

The `sql` methods on `DataFrame` and `LazyFrame` can no longer access global variables. These
methods should be used for operating on the frame itself. For global access, there is now the
top-level `sql` function.

**Example**

Before:

```pycon
>>> df1 = pl.DataFrame({"id1": [1, 2]})
>>> df2 = pl.DataFrame({"id2": [3, 4]})
>>> df1.sql("SELECT * FROM df1 CROSS JOIN df2")
shape: (4, 2)
┌─────┬─────┐
│ id1 ┆ id2 │
│ --- ┆ --- │
│ i64 ┆ i64 │
╞═════╪═════╡
│ 1   ┆ 3   │
│ 1   ┆ 4   │
│ 2   ┆ 3   │
│ 2   ┆ 4   │
└─────┴─────┘
```

After:

```pycon
>>> df1.sql("SELECT * FROM df1 CROSS JOIN df2")
Traceback (most recent call last):
...
polars.exceptions.SQLInterfaceError: relation 'df1' was not found
```

Use instead:

```pycon
>>> pl.sql("SELECT * FROM df1 CROSS JOIN df2", eager=True)
shape: (4, 2)
┌─────┬─────┐
│ id1 ┆ id2 │
│ --- ┆ --- │
│ i64 ┆ i64 │
╞═════╪═════╡
│ 1   ┆ 3   │
│ 1   ┆ 4   │
│ 2   ┆ 3   │
│ 2   ┆ 4   │
└─────┴─────┘
```

### Remove re-export of type aliases

We have a lot of type aliases defined in the `polars.type_aliases` module. Some of these were
re-exported at the top-level and in the `polars.datatypes` module. These re-exports have been
removed.

We plan on adding a public `polars.typing` module in the future with a number of curated type
aliases. Until then, please define your own type aliases, or import from our `polars.type_aliases`
module. Note that the `type_aliases` module is not technically public, so use at your own risk.

**Example**

Before:

```python
def foo(dtype: pl.PolarsDataType) -> None: ...
```

After:

```python
PolarsDataType = pl.DataType | type[pl.DataType]

def foo(dtype: PolarsDataType) -> None: ...
```

### Streamline optional dependency definitions in `pyproject.toml`

We revisited to optional dependency definitions and made some minor changes. If you were using the
extras `fastexcel`, `gevent`, `matplotlib`, or `async`, this is a breaking change. Please update
your Polars installation to use the new extras.

**Example**

Before:

```bash
pip install 'polars[fastexcel,gevent,matplotlib]'
```

After:

```bash
pip install 'polars[calamine,async,graph]'
```

## Deprecations

### Issue `PerformanceWarning` when LazyFrame properties `schema/dtypes/columns/width` are used

Recent improvements to the correctness of the schema resolving in the lazy engine have had
significant performance impact on the cost of resolving the schema. It is no longer 'free' - in
fact, in complex pipelines with lazy file reading, resolving the schema can be relatively expensive.

Because of this, the schema-related properties on LazyFrame were no longer good API design.
Properties represent information that is already available, and just needs to be retrieved. However,
for the LazyFrame properties, accessing these may have significant performance cost.

To solve this, we added the `LazyFrame.collect_schema` method, which retrieves the schema and
returns a `Schema` object. The properties raise a `PerformanceWarning` and tell the user to use
`collect_schema` instead. We chose not to deprecate the properties for now to facilitate writing
code that is generic for both DataFrames and LazyFrames.
