> ## Documentation Index
> Fetch the complete documentation index at: https://docs.activeviam.com/llms.txt
> Use this file to discover all available pages before exploring further.

> Detailed list of all changes in the Atoti Python SDK

# Changelog

This is a detailed list of all changes.
For a higher level view, see the [Release notes](./release_notes).

Atoti Python SDK implements [Atoti version policy](https://docs.activeviam.com/atoti-eos.html) in a specific manner to follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## 6.2.0b0

<Warning>
  This is a preproduction release, see [https://docs.activeviam.com/preproduction-releases](https://docs.activeviam.com/preproduction-releases)
</Warning>

Released on Jul 03, 2026.

### Added

* [`atoti.SessionConfig.base_path`](./api/atoti.config.session_config#atoti.SessionConfig.base_path) <sup>[16](#python-917)</sup>.
* [`atoti_jdbc.JdbcLoad.error_handling`](./api/atoti_jdbc.jdbc_load#atoti_jdbc.JdbcLoad.error_handling) to control what happens when loading invalid rows <sup>[3](#pivot-13841)</sup>.
* [`atoti.CsvLoad.error_handling`](./api/atoti.data_load.csv_load#atoti.CsvLoad.error_handling) to control what happens when loading invalid rows <sup>[4](#pivot-13961)</sup>.
* [`atoti.Cube.feeding`](./api/atoti.Cube.feeding#atoti.Cube.feeding) and [`atoti.Session.create_cube()`](./api/atoti.Session.create_cube#atoti.Session.create_cube)’s *feeding* parameter <sup>[13](#python-896)</sup>.
* [`atoti.aggregate_provider.aggregate_providers.AggregateProviders.recommender`](./api/atoti.aggregate_provider.aggregate_providers.AggregateProviders.recommender#atoti.aggregate_provider.aggregate_providers.AggregateProviders.recommender) to configure and retrieve AI-optimized aggregate provider recommendations based on the cube’s query history <sup>[14](#python-668)</sup>.
* Atoti Intelligence can now be enabled without configuring an LLM provider <sup>[10](#python-859)</sup>.
  Pass [`AiConfig`](./api/atoti_ai.ai_config#atoti_ai.AiConfig) with no arguments (leaving `connection` and `chat` as `None`).
  AutoExplain still works but returns a default, non-AI summary, and the chat feature is disabled.
  Only the `atoti[ai]` extra is required — no provider extra is needed <sup>[10](#python-859)</sup>.
  ```python theme={"languages":{"custom":["/engine/python-sdk/0.9/languages/pycon.tmLanguage.json"]}}
  from atoti_ai import AiConfig

  with tt.experimental({"ai"}):
      session = tt.Session(ai=AiConfig())
  ```

### Changed

* Bumped [`atoti-jupyter`](./api/atoti_jupyterlab#module-atoti_jupyterlab)’s minimum required JupyterLab version to 4.6.0.
* The default error handling for [`atoti.CsvLoad.error_handling`](./api/atoti.data_load.csv_load#atoti.CsvLoad.error_handling) and [`atoti_jdbc.JdbcLoad.error_handling`](./api/atoti_jdbc.jdbc_load#atoti_jdbc.JdbcLoad.error_handling) is now `"fail"` instead of `"log"` <sup>[3](#pivot-13841)</sup> <sup>[4](#pivot-13961)</sup>.
* Upgraded minimum Python required version to 3.12 as per [NEP 29](https://numpy.org/neps/nep-0029-deprecation_policy.html#support-table) <sup>[11](#python-810)</sup>.
* Upgraded minimum Pandas required version to 3.0.3 <sup>[7](#python-740)</sup>.
  Datetime and timedelta columns now use microsecond (`us`) precision instead of nanosecond (`ns`) precision, both when loading [`DataFrame`](https://pandas.pydata.org/pandas-docs/version/2.3/reference/api/pandas.DataFrame.html#pandas.DataFrame) objects into Atoti and when query results are returned as DataFrames.
  Arrow `large_string` columns are accepted on read in addition to the existing `string` type.
* [`atoti_ai_openai.ConnectionConfig`](./api/atoti_ai_openai.connection_config#atoti_ai_openai.ConnectionConfig) gained [`custom_headers`](./api/atoti_ai_openai.connection_config#atoti_ai_openai.ConnectionConfig.custom_headers), [`max_retries`](./api/atoti_ai_openai.connection_config#atoti_ai_openai.ConnectionConfig.max_retries), and [`timeout`](./api/atoti_ai_openai.connection_config#atoti_ai_openai.ConnectionConfig.timeout) while losing `project_id`.
  [`atoti_ai_openai.ChatConfig`](./api/atoti_ai_openai.chat_config#atoti_ai_openai.ChatConfig) lost `completions_path`, `http_headers`, and its `retry_*` attributes.
  The connection’s `base_url` must now include the API path (such as `/v1`), and HTTP headers, retries, and timeouts are configured on the connection instead <sup>[6](#pivot-13976)</sup>.
* When [`AiConfig`](./api/atoti_ai.ai_config#atoti_ai.AiConfig) is set, the session’s MCP server now exposes an OAuth 2.1 flow by default, so MCP clients such as Claude Desktop or Claude Code can authenticate without a manually provisioned Bearer token.
  If the session delegates authentication to an OIDC provider ([`OidcConfig`](./api/atoti.config.security.oidc_config#atoti.OidcConfig)), the MCP server reuses it (`external` mode: clients authenticate against that provider, which already guards the MCP endpoint).
  Otherwise the MCP server acts as its own OAuth 2.1 authorization server (`self-issued` mode: a browser login and consent against the session’s own authentication).
  HTTP Basic authentication on the MCP endpoint keeps working, so programmatic and headless clients are unaffected.
  Override with the `atoti.server.endpoint.mcp.oauth2.*` server properties <sup>[5](#pivot-13970)</sup>.
* Setting [`atoti.Hierarchy.virtual`](./api/atoti.Hierarchy.virtual#atoti.Hierarchy.virtual) to `True` on an existing hierarchy is now applied at runtime <sup>[8](#pivot-14011)</sup>.
* Upgraded Atoti UI and Atoti Admin UI to [5.2.24](https://docs.activeviam.com/products/atoti/ui/5.2/docs/changelog/#5224).
* The Atoti UI app is served in development mode if [`__debug__`](https://docs.python.org/3/library/constants.html#debug__) is `True`.
  See [deployment setup](./getting_started/deployment/deployment_setup#python-optimized-mode).
* AutoExplain and the chat feature are now provided by the new `atoti[ai]` extra (backed by the `atoti-client-ai` and `atoti-server-ai` packages).
  They are no longer included in the `atoti[ai-amazon-bedrock]` and `atoti[ai-openai]` extras <sup>[10](#python-859)</sup>.

  Users who use AutoExplain or chat with a provider extra must also install `ai`:

  ```diff theme={"languages":{"custom":["/engine/python-sdk/0.9/languages/pycon.tmLanguage.json"]}}
  - pip install "atoti[ai-amazon-bedrock]"
  + pip install "atoti[ai-amazon-bedrock,ai]"
  ```
* `atoti.AiConfig` has moved to [`atoti_ai.AiConfig`](./api/atoti_ai.ai_config#atoti_ai.AiConfig), provided by the new `atoti-client-ai` package included in the `atoti[ai]` extra.
  The `ChatConfig` and `ConnectionConfig` base classes have moved from `atoti.config.ai` to [`atoti_ai`](./api/atoti_ai#module-atoti_ai) as well <sup>[10](#python-859)</sup>.
  ```diff theme={"languages":{"custom":["/engine/python-sdk/0.9/languages/pycon.tmLanguage.json"]}}
  - session = tt.Session(ai=tt.AiConfig())
  + from atoti_ai import AiConfig
  + session = tt.Session(ai=AiConfig())
  ```
* The [`Parquet plugin`](./api/atoti_parquet#module-atoti_parquet) is not installed by default anymore.

### Deprecated

### Removed

* Publishing of Conda packages.
  Install the [Python packages](./getting_started/installation) with [uv](https://docs.astral.sh/uv) (or [pip](https://pip.pypa.io)) instead.

  It is still possible to use a Conda environment by installing the packages with `pip`:

  ```diff theme={"languages":{"custom":["/engine/python-sdk/0.9/languages/pycon.tmLanguage.json"]}}
    conda create --name my-env
    conda activate my-env
  - conda install atoti
  + pip install atoti
  ```
* `atoti_directquery_databricks.ConnectionConfig.array_sum_agg_function_name`, `atoti_directquery_databricks.ConnectionConfig.array_long_agg_function_name`, `atoti_directquery_databricks.ConnectionConfig.array_short_agg_function_name`, and `atoti_directquery_databricks.ConnectionConfig.array_sum_product_agg_function_name`.
  Spark UDAFs API has been deprecated as it is not recommended by Databricks anymore, use :mod:`array conversion <atoti.directquery.array_conversion>` instead <sup>[19](#python-817)</sup>.
* `atoti_directquery_snowflake.ConnectionConfig.array_agg_wrapper_function_name`.
  The built-in Snowflake `ARRAY_AGG` function is now faster, so the wrapper UDF is no longer needed <sup>[1](#pivot-10231)</sup>.
* `atoti.QueryCube.unload_members_from_data_cube`.
  Member unloading from a query cube has been removed <sup>[9](#pivot-14028)</sup>.
* Inferring the table name from the file path in [`atoti.Session.read_csv()`](./api/atoti.Session.read_csv#atoti.Session.read_csv).
  Pass a *table\_name* argument instead.
* The `atoti-aws`, `atoti-azure`, and `atoti-gcp` packages (and the `atoti[aws]`, `atoti[azure]`, and `atoti[gcp]` extras).
  Install `atoti-storage-aws`, `atoti-storage-azure`, or `atoti-storage-gcp` instead (i.e. replace `atoti[aws]` with `atoti[storage-aws]`, `atoti[azure]` with `atoti[storage-azure]`, and `atoti[gcp]` with `atoti[storage-gcp]`).
* The `atoti-server-aws`, `atoti-server-azure`, and `atoti-server-gcp` packages.
  Install `atoti-server-storage-aws`, `atoti-server-storage-azure`, or `atoti-server-storage-gcp` instead.
* `Table.columns` and `ExternalTable.columns`.
  Use `list(table)` instead.
* `table.append(rows)`.
  Use `table.load(pd.DataFrame(rows, columns=list(table)))` instead.
* The [`Table`](./api/atoti.table#atoti.Table) data-loading shortcuts.
  Use [`atoti.Table.load()`](./api/atoti.Table.load#atoti.Table.load) (or [`atoti.Table.stream()`](./api/atoti.Table.stream#atoti.Table.stream) for Kafka) with the matching load/stream description instead:
  * `table.load_csv(path)` → `table.load(tt.CsvLoad(path))`
  * `table.load_pandas(dataframe)` → `table.load(dataframe)`
  * `table.load_arrow(arrow_table)` → `table.load(arrow_table)`
  * `table.load_numpy(array)` → `table.load(pd.DataFrame(array, columns=list(table)))`
  * `table.load_parquet(path)` → `table.load(atoti_parquet.ParquetLoad(path))`
  * `table.load_spark(dataframe)` → `table.load(dataframe.toPandas())`
  * `table.load_kafka(...)` → `table.stream(atoti_kafka.KafkaStream(...))`
  * `table.load_sql(query)` → `table.load(atoti_jdbc.JdbcLoad(query))`
* `Session.read_parquet()`, `Session.read_spark()`, `Session.read_numpy()`, and `Session.read_sql()`.
  Use [`atoti.Session.create_table()`](./api/atoti.Session.create_table#atoti.Session.create_table) together with [`atoti.Table.load()`](./api/atoti.Table.load#atoti.Table.load) and the matching load description instead (e.g. [`atoti_parquet.ParquetLoad`](./api/atoti_parquet.parquet_load#atoti_parquet.ParquetLoad) or [`atoti_jdbc.JdbcLoad`](./api/atoti_jdbc.jdbc_load#atoti_jdbc.JdbcLoad)); for Spark and NumPy, build a [`pandas.DataFrame`](https://pandas.pydata.org/pandas-docs/version/2.3/reference/api/pandas.DataFrame.html#pandas.DataFrame) and use [`atoti.Session.read_pandas()`](./api/atoti.Session.read_pandas#atoti.Session.read_pandas).
* The `types` parameter of [`atoti.Session.create_table()`](./api/atoti.Session.create_table#atoti.Session.create_table), [`read_csv()`](./api/atoti.Session.read_csv#atoti.Session.read_csv), [`read_pandas()`](./api/atoti.Session.read_pandas#atoti.Session.read_pandas), and [`read_arrow()`](./api/atoti.Session.read_arrow#atoti.Session.read_arrow).
  Use the `data_types` parameter instead.
* The `timeout` parameter of [`atoti.Cube.query()`](./api/atoti.Cube.query#atoti.Cube.query) and [`atoti.Session.query_mdx()`](./api/atoti.Session.query_mdx#atoti.Session.query_mdx).
  Pass a `queriesTimeLimit` in the `context` argument instead.
* `Hierarchy.levels`.
  Iterate on the hierarchy instead.
* The `Hierarchy.visible` setter.
  Use [`atoti.Hierarchy.viewers`](./api/atoti.Hierarchy.viewers#atoti.Hierarchy.viewers) instead.
* Deleting a [`Level`](./api/atoti.level#atoti.Level) (`del cube.levels[...]`).
  Redefine its [`Hierarchy`](./api/atoti.hierarchy#atoti.Hierarchy) instead.
* `Cube.aggregates_cache`.
  Use [`atoti.Cube.aggregate_cache`](./api/atoti.Cube.aggregate_cache#atoti.Cube.aggregate_cache) instead.
* The [`atoti.Measure.description`](./api/atoti.Measure.description#atoti.Measure.description) deleter (`del measure.description`).
  Set `measure.description = ""` instead.
* `Session.start_transaction()`.
  Use [`session.tables.data_transaction()`](./api/atoti.tables.Tables.data_transaction#atoti.tables.Tables.data_transaction) instead.
* `Session.port`.
  Use [`atoti.Session.url`](./api/atoti.Session.url#atoti.Session.url) instead (parsing it with [`urllib.parse.urlparse()`](https://docs.python.org/3/library/urllib.parse.html#urllib.parse.urlparse) if necessary).
* `session.security.restrictions`.
  Use [`session.tables.restrictions`](./api/atoti.tables.Tables.restrictions#atoti.tables.Tables.restrictions) instead.
* `security.basic`.
  Use [`basic_authentication`](./api/atoti.security.Security.basic_authentication#atoti.security.Security.basic_authentication) instead.
* The `method` parameter of `date_shift()`.
  Use its `fallback` parameter instead.
* The overload of `copy()` taking `hierarchy`, `member_paths`, and `consolidation_factors`.
  Use `tt.consolidate()` instead.
* Importing `ParquetLoad` from `atoti`.
  Import it from [`atoti_parquet`](./api/atoti_parquet#module-atoti_parquet) instead.
* The `copy_tutorial` notebook script.
  Follow the tutorial from the [online documentation](https://docs.activeviam.com/products/atoti/python-sdk/latest/getting_started/) instead.
* The `atoti.pyapi` module.
  Import [`Request`](./api/atoti.endpoint.request#atoti.endpoint.Request) (previously exposed as `HttpRequest`) and [`atoti.User`](./api/atoti.user#atoti.User) directly instead.
* Leaving `username_case_conversion` unset on [`atoti.LdapConfig`](./api/atoti.config.security.ldap_config#atoti.LdapConfig) and [`atoti.KerberosConfig`](./api/atoti.config.security.kerberos_config#atoti.KerberosConfig).
  It is now required: pass `"upper"` or `"lower"`.
* The deprecation warning emitted when [`atoti.AggregateProvider`](./api/atoti.aggregate_provider.aggregate_provider#atoti.AggregateProvider) is given multiple levels belonging to the same hierarchy.
  This misconfiguration now raises a `ValueError`.

### Fixed

* Creating a DirectQuery cube with `mode="no_measures"` and a `filter` no longer sends unfiltered `APPROX_COUNT_DISTINCT` queries for virtual hierarchy detection.
  The cube filter is now applied to these statistics queries, preventing timeouts on large tables <sup>[12](#python-869)</sup>.
* `LoggingCsvHealthEventHandler` no longer logs spurious errors when non-CSV health events are dispatched to it <sup>[15](#python-913)</sup>.
* [`JdbcPingDiscoveryProtocol`](./api/atoti_jdbc.jdbc_ping_discovery_protocol#atoti_jdbc.JdbcPingDiscoveryProtocol) backed by an embedded H2 file database intermittently failing to form the cluster, because the query and data nodes, which run in separate JVMs, could not open the discovery database concurrently.
  `AUTO_SERVER=TRUE` is now enabled automatically for embedded H2 databases so that the query and data nodes can access the discovery table concurrently <sup>[17](#python-926)</sup>.

### Security

* AWS S3: The commitment policy is now `REQUIRE_ENCRYPT_REQUIRE_DECRYPT`, enforcing authenticated encryption for both reads and writes.
  Existing S3 data encrypted with older AWS encryption SDK clients (V1/V2) must be re-encrypted before upgrading <sup>[2](#pivot-13185)</sup>.

### Internal issue tracker references

* <a id="pivot-10231">**\[1]**</a> `PIVOT-10231`
* <a id="pivot-13185">**\[2]**</a> `PIVOT-13185`
* <a id="pivot-13841">**\[3]**</a> `PIVOT-13841`
* <a id="pivot-13961">**\[4]**</a> `PIVOT-13961`
* <a id="pivot-13970">**\[5]**</a> `PIVOT-13970`
* <a id="pivot-13976">**\[6]**</a> `PIVOT-13976`
* <a id="python-740">**\[7]**</a> `PYTHON-740`
* <a id="pivot-14011">**\[8]**</a> `PIVOT-14011`
* <a id="pivot-14028">**\[9]**</a> `PIVOT-14028`
* <a id="python-859">**\[10]**</a> `PYTHON-859`
* <a id="python-810">**\[11]**</a> `PYTHON-810`
* <a id="python-869">**\[12]**</a> `PYTHON-869`
* <a id="python-896">**\[13]**</a> `PYTHON-896`
* <a id="python-668">**\[14]**</a> `PYTHON-668`
* <a id="python-913">**\[15]**</a> `PYTHON-913`
* <a id="python-917">**\[16]**</a> `PYTHON-917`
* <a id="python-926">**\[17]**</a> `PYTHON-926`

## 0.9.14

Released on April 17, 2026.

### Added

* OpenTelemetry spans around the main functions and methods of the library.
  See [`atoti_observability`](./api/atoti_observability#module-atoti_observability) for example traces.

### Changed

* Upgraded Atoti Server to [6.1.19](https://docs.activeviam.com/products/atoti/server/6.1/docs/release/whats_new/#6119).

### Deprecated

* atoti\_directquery\_databricks.ConnectionConfig.array\_sum\_agg\_function\_name, atoti\_directquery\_databricks.ConnectionConfig.array\_long\_agg\_function\_name, atoti\_directquery\_databricks.ConnectionConfig.array\_short\_agg\_function\_name, and atoti\_directquery\_databricks.ConnectionConfig.array\_sum\_product\_agg\_function\_name.
  Spark UDAFs API has been deprecated as it is not recommended by Databricks anymore, use [`array conversion`](./api/atoti.directquery.array_conversion#module-atoti.directquery.array_conversion) instead <sup>[19](#python-817)</sup>.

### Fixed

* [`QueryCube`](./api/atoti.distribution.query_cube#atoti.QueryCube)’s hierarchies not being [`visible`](./api/atoti.Hierarchy.visible#atoti.Hierarchy.visible) by default. <sup>[18](#python-798)</sup>.
* [`ModuleNotFoundError`](https://docs.python.org/3/library/exceptions.html#ModuleNotFoundError) raised by [`atoti-client`](./api/atoti#module-atoti) when [`atoti-client-parquet`](./api/atoti_parquet#module-atoti_parquet) is not installed.

### Internal issue tracker references

* <a id="python-798">**\[18]**</a> `PYTHON-798`
* <a id="python-817">**\[19]**</a> `PYTHON-817`

## 0.9.13

Released on March 10, 2026.

### Added

* [`shift()`](./api/atoti.function.shift#atoti.shift)’s *mode* parameter <sup>[21](#python-703)</sup>.
* Documentation for [`atoti-observability`](./api/atoti_observability#module-atoti_observability).
* Documentation for the ROLE\_MEASURE\_LINEAGE role in [`atoti.security.Security`](./api/atoti.security.security#atoti.security.Security).
* [`atoti_jdbc.JdbcPingDiscoveryProtocol.update_store_on_view_change`](./api/atoti_jdbc.jdbc_ping_discovery_protocol#atoti_jdbc.JdbcPingDiscoveryProtocol.update_store_on_view_change) <sup>[25](#python-789)</sup>.

### Changed

* Upgraded Atoti Server to [6.1.18](https://docs.activeviam.com/products/atoti/server/6.1/docs/release/whats_new/#6118).
* Upgraded Atoti UI and Atoti Admin UI to [5.2.20](https://docs.activeviam.com/products/atoti/ui/5.2/docs/changelog/#5220).
* Renamed cloud storage packages:

  * `atoti-client-aws` → `atoti-client-storage-aws`
  * `atoti-server-aws` → `atoti-server-storage-aws`
  * `atoti-client-azure` → `atoti-client-storage-azure`
  * `atoti-server-azure` → `atoti-server-storage-azure`
  * `atoti-client-gcp` → `atoti-client-storage-gcp`
  * `atoti-server-gcp` → `atoti-server-storage-gcp`

  The module names have also changed:

  * `atoti_aws` → [`atoti_storage_aws`](./api/atoti_storage_aws#module-atoti_storage_aws)
  * `atoti_azure` → [`atoti_storage_azure`](./api/atoti_storage_azure#module-atoti_storage_azure)
  * `atoti_gcp` → [`atoti_storage_gcp`](./api/atoti_storage_gcp#module-atoti_storage_gcp)

  ```diff theme={"languages":{"custom":["/engine/python-sdk/0.9/languages/pycon.tmLanguage.json"]}}
  - uv add "atoti[aws]"
  + uv add "atoti[storage-aws]"
  ```

  ```diff theme={"languages":{"custom":["/engine/python-sdk/0.9/languages/pycon.tmLanguage.json"]}}
  - from atoti_aws import ClientSideEncryptionConfig
  + from atoti_storage_aws import ClientSideEncryptionConfig
  ```

### Deprecated

* `atoti-client-aws`, `atoti-server-aws`, and the `atoti_aws` module.
  Use `atoti-client-storage-aws`, `atoti-server-storage-aws`, and [`atoti_storage_aws`](./api/atoti_storage_aws#module-atoti_storage_aws) instead.
* `atoti-client-azure`, `atoti-server-azure`, and the `atoti_azure` module.
  Use `atoti-client-storage-azure`, `atoti-server-storage-azure`, and [`atoti_storage_azure`](./api/atoti_storage_azure#module-atoti_storage_azure) instead.
* `atoti-client-gcp`, `atoti-server-gcp`, and the `atoti_gcp` module.
  Use `atoti-client-storage-gcp`, `atoti-server-storage-gcp`, and [`atoti_storage_gcp`](./api/atoti_storage_gcp#module-atoti_storage_gcp) instead.

### Fixed

* [`NotImplementedError`](https://docs.python.org/3/library/exceptions.html#NotImplementedError) raised when creating a `==` condition on a column operation <sup>[23](#python-774)</sup>, <sup>[24](#python-775)</sup>.
* [`atoti.Column.data_type`](./api/atoti.Column.data_type#atoti.Column.data_type) and [`atoti.Column.default_value`](./api/atoti.Column.default_value#atoti.Column.default_value) raising an error when accessed on a [`connected session`](./api/atoti.Session.connect#atoti.Session.connect) using the `"Object"` data type <sup>[20](#pivot-12311)</sup>.
* Setting MDX-related context values with [`shared_context`](./api/atoti.Cube.shared_context#atoti.Cube.shared_context) not being reflected in subsequent access to [`shared_context`](./api/atoti.Cube.shared_context#atoti.Cube.shared_context) in data cubes and raising an error in query cubes <sup>[22](#python-768)</sup>.

### Internal issue tracker references

* <a id="pivot-12311">**\[20]**</a> `PIVOT-12311`
* <a id="python-703">**\[21]**</a> `PYTHON-703`
* <a id="python-768">**\[22]**</a> `PYTHON-768`
* <a id="python-774">**\[23]**</a> `PYTHON-774`
* <a id="python-775">**\[24]**</a> `PYTHON-775`
* <a id="python-789">**\[25]**</a> `PYTHON-789`

## 0.9.12

Released on January 23, 2026.

### Added

* [`atoti.SecurityConfig.login`](./api/atoti.config.security.security_config#atoti.SecurityConfig.login).

### Changed

* Upgraded Atoti Server to [6.1.16](https://docs.activeviam.com/products/atoti/server/6.1/docs/release/whats_new/#6116).
* Upgraded Atoti UI and Atoti Admin UI to [5.2.18](https://docs.activeviam.com/products/atoti/ui/5.2/docs/changelog/#5218).
* Loading Apache Parquet files requires the new `atoti-client-parquet` and `atoti-server-parquet` packages <sup>[26](#pivot-12663)</sup>.

  Installing the `atoti` package also installs these packages but the next breaking release will make them opt-in.

  ```diff theme={"languages":{"custom":["/engine/python-sdk/0.9/languages/pycon.tmLanguage.json"]}}
  - uv add "atoti"
  + uv add "atoti[parquet]"
  ```

  If you do no use Parquet files and wish to reduce the size of your dependencies, you can skip the installation of the `atoti` package and manually install the `atoti-client` and `atoti-server` packages instead:

  ```diff theme={"languages":{"custom":["/engine/python-sdk/0.9/languages/pycon.tmLanguage.json"]}}
  - # Also installs atoti-client-parquet and atoti-server-parquet.
  - uv add "atoti"
  + # Does not install atoti-client-parquet and atoti-server-parquet.
  + uv add "atoti-client" "atoti-server"
  ```

  Or, if you use some extras:

  ```diff theme={"languages":{"custom":["/engine/python-sdk/0.9/languages/pycon.tmLanguage.json"]}}
  - uv add "atoti[jdbc]"
  + uv add "atoti-client[jdbc]" "atoti-server[jdbc]"
  ```

### Deprecated

* `atoti.ParquetLoad` <sup>[26](#pivot-12663)</sup>.
  Use [`atoti_parquet.ParquetLoad`](./api/atoti_parquet.parquet_load#atoti_parquet.ParquetLoad) instead:
  ```diff theme={"languages":{"custom":["/engine/python-sdk/0.9/languages/pycon.tmLanguage.json"]}}
  - from atoti import ParquetLoad
  + from atoti_parquet import ParquetLoad
  ```
* `Session.read_parquet` since support for Apache Parquet will become opt-in <sup>[26](#pivot-12663)</sup>.
  Use [`infer_data_types()`](./api/atoti.tables.Tables.infer_data_types#atoti.tables.Tables.infer_data_types), [`create_table()`](./api/atoti.Session.create_table#atoti.Session.create_table), and [`load()`](./api/atoti.Table.load#atoti.Table.load) instead:
  ```diff theme={"languages":{"custom":["/engine/python-sdk/0.9/languages/pycon.tmLanguage.json"]}}
  - table = session.read_parquet(path, table_name="Example")
  + parquet_load = ParquetLoad(path)
  + data_types = session.tables.infer_data_types(parquet_load)
  + table = session.create_table("Example", data_types=data_types)
  + table.load(parquet_load)
  ```
* [`atoti.Session.query_mdx()`](./api/atoti.Session.query_mdx#atoti.Session.query_mdx) and [`atoti.Cube.query()`](./api/atoti.Cube.query#atoti.Cube.query)’s *timeout* parameter <sup>[27](#python-447)</sup>.

  The default value has also been changed from `datetime.timedelta(seconds=30)` to `None` to automatically use [`shared_context`](./api/atoti.Cube.shared_context#atoti.Cube.shared_context)’s `queriesTimeLimit` .

  Use the *context* parameter to specify a custom timeout:

  ```diff theme={"languages":{"custom":["/engine/python-sdk/0.9/languages/pycon.tmLanguage.json"]}}
  - session.query_mdx(mdx, timeout=datetime.timedelta(seconds=10))
  + session.query_mdx(mdx, context={"queriesTimeLimit": 10})
  ```

  ```diff theme={"languages":{"custom":["/engine/python-sdk/0.9/languages/pycon.tmLanguage.json"]}}
  - cube.query(m["contributors.COUNT"], timeout=datetime.timedelta(seconds=10))
  + cube.query(m["contributors.COUNT"], context={"queriesTimeLimit": 10})
  ```

### Fixed

* Atoti Admin UI connecting to wrong server due to wrong order of WebJars in the server’s classpath <sup>[29](#python-755)</sup>.
* Hierarchies disappearing from the Data model tree when editing the data model of a [`secured session`](./api/atoti.config.security.security_config#atoti.SecurityConfig) <sup>[28](#python-742)</sup>.

### Internal issue tracker references

* <a id="pivot-12663">**\[26]**</a> `PIVOT-12663`
* <a id="python-447">**\[27]**</a> `PYTHON-447`
* <a id="python-742">**\[28]**</a> `PYTHON-742`
* <a id="python-755">**\[29]**</a> `PYTHON-755`

## 0.9.11

Released on December 16, 2025.

### Added

* [`atoti.Hierarchy.description`](./api/atoti.Hierarchy.description#atoti.Hierarchy.description) <sup>[33](#python-733)</sup>.
* `pandas<3.0.0rc0` upper bound to avoid [breaking changes from the new str dtype](https://pandas.pydata.org/docs/dev/whatsnew/v3.0.0.html#dedicated-string-data-type-by-default) <sup>[35](#python-739)</sup>.

### Changed

* Upgraded Atoti Server to [6.1.15](https://docs.activeviam.com/products/atoti/server/6.1/docs/release/whats_new/#6115).
* Upgraded Atoti UI and Atoti Admin UI to [5.2.17](https://docs.activeviam.com/products/atoti/ui/5.2/docs/changelog/#5217).
* [`atoti.Measure.description`](./api/atoti.Measure.description#atoti.Measure.description) returns `""` instead of `None` <sup>[33](#python-733)</sup>.
* Everything related to [`Security`](./api/atoti.security.security#atoti.security.Security) is kept in memory instead of being persisted in the [`user_content_storage`](./api/atoti.config.session_config#atoti.SessionConfig.user_content_storage) <sup>[30](#python-716)</sup>.

  This makes the API more consistent since all the other methods on [`atoti.Session`](./api/atoti.session#atoti.Session) and its related classes never persisted their changes either.
  This improves performance, both when configuring these properties but also when executing queries.

  <Note>
    [`Basic authentication credentials`](./api/atoti.security.basic_authentication_security.BasicAuthenticationSecurity.credentials#atoti.security.basic_authentication_security.BasicAuthenticationSecurity.credentials) are unaffected since, for security reasons, they were already only kept in memory.
  </Note>

### Deprecated

* [`atoti.Measure.description`](./api/atoti.Measure.description#atoti.Measure.description)’s deleter <sup>[33](#python-733)</sup>.
  Set the description to a blank string instead:
  ```diff theme={"languages":{"custom":["/engine/python-sdk/0.9/languages/pycon.tmLanguage.json"]}}
  - del measure.description
  + measure.description = ""
  ```

### Fixed

* Data model search bar display issue in JupyterLab extension <sup>[36](#python-745)</sup>.
* Truncated response bodies when using [`proxy`](./api/atoti.Session.proxy#atoti.Session.proxy) or [`endpoint()`](./api/atoti.Session.endpoint#atoti.Session.endpoint) <sup>[31](#python-717)</sup>.
* `This operation is not supported while batching mutations` error raised when requesting an [`endpoint()`](./api/atoti.Session.endpoint#atoti.Session.endpoint) <sup>[34](#python-737)</sup>.
* Users configured through [`basic_authentication`](./api/atoti.security.Security.basic_authentication#atoti.security.Security.basic_authentication) unable to connect when [`the configured LDAP server`](./api/atoti.config.security.ldap_config#atoti.LdapConfig) is unreachable <sup>[32](#python-728)</sup>.

### Internal issue tracker references

* <a id="python-716">**\[30]**</a> `PYTHON-716`
* <a id="python-717">**\[31]**</a> `PYTHON-717`
* <a id="python-728">**\[32]**</a> `PYTHON-728`
* <a id="python-733">**\[33]**</a> `PYTHON-733`
* <a id="python-737">**\[34]**</a> `PYTHON-737`
* <a id="python-739">**\[35]**</a> `PYTHON-739`
* <a id="python-745">**\[36]**</a> `PYTHON-745`

## 0.9.10

Released on October 31, 2025.

### Added

* Support for [Python 3.14](https://docs.python.org/3/whatsnew/3.14.html) <sup>[38](#python-712)</sup>.
* [Dependencies](./dependencies) page.

### Changed

* Upgraded Atoti Server to [6.1.13](https://docs.activeviam.com/products/atoti/server/6.1/docs/release/whats_new/#6113).
* Upgraded Atoti UI and Atoti Admin UI to [5.2.15](https://docs.activeviam.com/products/atoti/ui/5.2/docs/changelog/#5215).
* Installation instructions with Conda to recommend Miniforge over Anaconda’s installer to not be tied to Anaconda’s license requirements <sup>[39](#python-715)</sup>.
* Configuring [`branding`](./api/atoti.config.session_config#atoti.SessionConfig.branding) does not remove the By ActiveViam signature at the bottom right corner of the app anymore.
  It is still possible to remove this signature through a custom [`app extension`](./api/atoti.config.session_config#atoti.SessionConfig.app_extensions) <sup>[37](#python-590)</sup>.

### Internal issue tracker references

* <a id="python-590">**\[37]**</a> `PYTHON-590`
* <a id="python-712">**\[38]**</a> `PYTHON-712`
* <a id="python-715">**\[39]**</a> `PYTHON-715`

## 0.9.9.2

Released on October 20, 2025.

<Note>
  This version is not published on PyPI, it is only available on [ActiveViam’s repository](./getting_started/installation#activeviam-repository).
</Note>

### Added

* ROLE\_ADMIN to [`atoti.Hierarchy.viewers`](./api/atoti.Hierarchy.viewers#atoti.Hierarchy.viewers)’s default value <sup>[41](#python-697)</sup>.

### Changed

* Improved planning of MDX queries requesting many specific cells of the cube <sup>[40](#pivot-12464)</sup>.
* Reduce number of requests made when [`representing`](https://docs.python.org/3/library/functions.html#repr) (or evaluating in a Jupyter notebook cell) cubes, levels, measures, etc of a [`connected session`](./api/atoti.Session.connect#atoti.Session.connect) <sup>[42](#python-698)</sup>.

### Fixed

* `SSL: CERTIFICATE_VERIFY_FAILED` error when requesting a custom [`endpoint()`](./api/atoti.Session.endpoint#atoti.Session.endpoint) on a [`Session`](./api/atoti.session#atoti.Session) configured with [`https`](./api/atoti.config.security.security_config#atoti.SecurityConfig.https) <sup>[43](#python-705)</sup>.

### Internal issue tracker references

* <a id="pivot-12464">**\[40]**</a> `PIVOT-12464`
* <a id="python-697">**\[41]**</a> `PYTHON-697`
* <a id="python-698">**\[42]**</a> `PYTHON-698`
* <a id="python-705">**\[43]**</a> `PYTHON-705`

## 0.9.9.1

Released on September 30, 2025.

<Note>
  This version is not published on PyPI, it is only available on [ActiveViam’s repository](./getting_started/installation#activeviam-repository).
</Note>

### Changed

* Improved planning of MDX queries requesting many specific cells of the cube <sup>[44](#pivot-12032)</sup>.

### Fixed

* `ArrayIndexOutOfBoundsException` when running some MDX queries <sup>[45](#pivot-12336)</sup>.
* `AssertionError: Output has already been set` when deleting a cube <sup>[47](#python-696)</sup>.
* `GraphQLClientGraphQLMultiError: Structural transaction must be committed by the thread starting it` when setting [`atoti.Measure.visible`](./api/atoti.Measure.visible#atoti.Measure.visible) inside a [`data_model_transaction()`](./api/atoti.Session.data_model_transaction#atoti.Session.data_model_transaction) <sup>[46](#python-691)</sup>.

### Internal issue tracker references

* <a id="pivot-12032">**\[44]**</a> `PIVOT-12032`
* <a id="pivot-12336">**\[45]**</a> `PIVOT-12336`
* <a id="python-691">**\[46]**</a> `PYTHON-691`
* <a id="python-696">**\[47]**</a> `PYTHON-696`

## 0.9.9

Released on September 22, 2025.

### Added

* [`plugin_measure()`](./api/atoti.function.plugin_measure#atoti.plugin_measure) <sup>[53](#python-634)</sup>.
* [`atoti.Session.client`](./api/atoti.Session.client#atoti.Session.client) <sup>[51](#python-629)</sup>.
* [`Spring Boot Admin plugin`](./api/atoti_spring_boot_admin#module-atoti_spring_boot_admin) <sup>[48](#pivot-12095)</sup>.
* [`atoti_directquery_snowflake.ConnectionConfig.main_warehouse_name`](./api/atoti_directquery_snowflake.connection_config#atoti_directquery_snowflake.ConnectionConfig.main_warehouse_name) <sup>[49](#python-592)</sup>.

### Changed

* Upgraded Atoti Server to [6.1.12](https://docs.activeviam.com/products/atoti/server/6.1.12/docs/release/whats_new/#6112).
* Upgraded Atoti UI and Atoti Admin UI to [5.2.13](https://docs.activeviam.com/products/atoti/ui/5.2/docs/changelog/#5213).
* Improved the logic used in [`atoti.Session.link`](./api/atoti.Session.link#atoti.Session.link) and [`atoti.Session.widget`](./api/atoti.Session.widget#atoti.Session.widget) to find the URL from which the [`Session`](./api/atoti.session#atoti.Session) can be reached from the JupyterLab extension <sup>[50](#python-621)</sup>.

  For instance, if Jupyter Server is running at [https://1.9.9.1:8888](https://1.9.9.1:8888) and the Atoti session is running on the same machine but on port 1337, the browser will try to reach the session from [https://1.9.9.1:1337](https://1.9.9.1:1337).

### Fixed

* Incorrect [`MdxQueryResult`](./api/atoti.mdx_query_result#atoti.MdxQueryResult) when disabling [light crossjoin](https://docs.activeviam.com/products/atoti/server/6.2/docs/mdx/engine_configuration/#light-crossjoin) <sup>[52](#python-632)</sup>.
* Regression introduced in version 0.9.8 where [`atoti.Session.endpoint`](./api/atoti.Session.endpoint#atoti.Session.endpoint) would not handle URL encoded characters correctly <sup>[54](#python-677)</sup>.
* Inability to use Basic Authentication in requests made to [`custom endpoints`](./api/atoti.Session.endpoint#atoti.Session.endpoint) when the session is secured with [`OIDC`](./api/atoti.config.security.oidc_config#atoti.OidcConfig) <sup>[55](#python-678)</sup>.

### Internal issue tracker references

* <a id="pivot-12095">**\[48]**</a> `PIVOT-12095`
* <a id="python-592">**\[49]**</a> `PYTHON-592`
* <a id="python-621">**\[50]**</a> `PYTHON-621`
* <a id="python-629">**\[51]**</a> `PYTHON-629`
* <a id="python-632">**\[52]**</a> `PYTHON-632`
* <a id="python-634">**\[53]**</a> `PYTHON-634`
* <a id="python-677">**\[54]**</a> `PYTHON-677`
* <a id="python-678">**\[55]**</a> `PYTHON-678`

## 0.9.8

Released on August 12, 2025.

### Added

* [`atoti.Session.proxy`](./api/atoti.Session.proxy#atoti.Session.proxy) and [`atoti.Session.user`](./api/atoti.Session.user#atoti.Session.user) <sup>[56](#python-545)</sup>.

### Changed

* Upgraded Atoti Server to [6.1.11](https://docs.activeviam.com/products/atoti/server/6.1.11/docs/release/whats_new/#6111).
* When [`atoti.JwtConfig.key_pair`](./api/atoti.config.security.jwt_config#atoti.JwtConfig.key_pair) is `None`, the automatically generated key pair will use 3072 bits instead of the old 2048 bits.

### Deprecated

* Requests to endpoints created with [`atoti.Session.endpoint`](./api/atoti.Session.endpoint#atoti.Session.endpoint) should now use `/proxy` instead of `/atoti/pyapi`.
  `/atoti/pyapi` remains available but logs a warning when used <sup>[56](#python-545)</sup>.
* The `atoti.pyapi` module is deprecated, import its classes directly from [`atoti.User`](./api/atoti.user#atoti.User) or [`atoti.endpoint.Request`](./api/atoti.endpoint.request#atoti.endpoint.Request) instead <sup>[56](#python-545)</sup>.

### Fixed

* Performance regression when editing measures inside a [`data_model_transaction()`](./api/atoti.Session.data_model_transaction#atoti.Session.data_model_transaction) <sup>[57](#python-650)</sup>.

### Internal issue tracker references

* <a id="python-545">**\[56]**</a> `PYTHON-545`
* <a id="python-650">**\[57]**</a> `PYTHON-650`

## 0.9.7

Released on July 01, 2025.

### Added

* [`atoti.Hierarchy.viewers`](./api/atoti.Hierarchy.viewers#atoti.Hierarchy.viewers) <sup>[60](#python-552)</sup>.
* [`atoti.Hierarchy.folder`](./api/atoti.Hierarchy.folder#atoti.Hierarchy.folder) <sup>[61](#python-593)</sup>.
* [`atoti.SecurityConfig.cors`](./api/atoti.config.security.security_config#atoti.SecurityConfig.cors) <sup>[59](#pivot-11652)</sup>.
* [`atoti.finance.irr()`](./api/atoti.finance.irr#atoti.finance.irr)’s *period* and *guess* parameters <sup>[64](#python-623)</sup>.

### Changed

* Upgraded Atoti Server to [6.1.10](https://docs.activeviam.com/products/atoti/server/6.1.10/docs/release/whats_new/#6110).
* Upgraded Atoti UI and Atoti Admin UI to [5.2.10](https://docs.activeviam.com/products/atoti/ui/5.2/docs/changelog/#5210).
* [`atoti.AggregateProvider.measures`](./api/atoti.aggregate_provider.aggregate_provider#atoti.AggregateProvider.measures) can be set to `None` to pre-aggregate all eligible measures <sup>[63](#python-615)</sup>.
* Measures can no longer be defined in a data [`Cube`](./api/atoti.cube#atoti.Cube) once it joined a distribution cluster.
  The measures must be defined before setting [`atoti.Session.clusters`](./api/atoti.Session.clusters#atoti.Session.clusters), or the session must be started with [`atoti.SessionConfig.ready`](./api/atoti.config.session_config#atoti.SessionConfig.ready) set to `False` with [`atoti.Session.ready`](./api/atoti.Session.ready#atoti.Session.ready) set to `True` only once all measures have been defined <sup>[58](#pivot-11612)</sup>.

### Deprecated

* [`atoti.Hierarchy.visible`](./api/atoti.Hierarchy.visible#atoti.Hierarchy.visible) setter <sup>[60](#python-552)</sup>.
  Use [`atoti.Hierarchy.viewers`](./api/atoti.Hierarchy.viewers#atoti.Hierarchy.viewers) instead:
  ```diff theme={"languages":{"custom":["/engine/python-sdk/0.9/languages/pycon.tmLanguage.json"]}}
  - hierarchy.visible = False
  + hierarchy.viewers.clear()
  - hierarchy.visible = True
  + hierarchy.viewers.add("ROLE_USER")
  ```
* Passing multiple levels of the same hierarchy to [`atoti.AggregateProvider.levels`](./api/atoti.aggregate_provider.aggregate_provider#atoti.AggregateProvider.levels).
  Only pass the deepest level instead:
  ```diff theme={"languages":{"custom":["/engine/python-sdk/0.9/languages/pycon.tmLanguage.json"]}}
  - tt.AggregateProvider(levels={l["Geography", "Country"], l["Geography", "City"]})
  + tt.AggregateProvider(levels={l["Geography", "City"]})
  ```
* Passing sequences to [`atoti.AggregateProvider.levels`](./api/atoti.aggregate_provider.aggregate_provider#atoti.AggregateProvider.levels) or [`atoti.AggregateProvider.measures`](./api/atoti.aggregate_provider.aggregate_provider#atoti.AggregateProvider.measures).
  Pass sets instead:
  ```diff theme={"languages":{"custom":["/engine/python-sdk/0.9/languages/pycon.tmLanguage.json"]}}
  - tt.AggregateProvider(levels=[l["foo"]], measures=[m["bar"]])
  + tt.AggregateProvider(levels={l["foo"]}, measures={m["bar"]})
  ```

### Fixed

* [`where()`](./api/atoti.function.where#atoti.where) rejecting *condition* if it contained a `~hierarchy.isin(...)` or `~level.isin(...)` leaf <sup>[62](#python-610)</sup>.
* [`atoti.finance.irr()`](./api/atoti.finance.irr#atoti.finance.irr) returning [`nan`](https://docs.python.org/3/library/math.html#math.nan) when the actual rate was too large <sup>[64](#python-623)</sup>.

### Internal issue tracker references

* <a id="pivot-11612">**\[58]**</a> `PIVOT-11612`
* <a id="pivot-11652">**\[59]**</a> `PIVOT-11652`
* <a id="python-552">**\[60]**</a> `PYTHON-552`
* <a id="python-593">**\[61]**</a> `PYTHON-593`
* <a id="python-610">**\[62]**</a> `PYTHON-610`
* <a id="python-615">**\[63]**</a> `PYTHON-615`
* <a id="python-623">**\[64]**</a> `PYTHON-623`

## 0.9.6

Released on May 17, 2025.

### Added

* [`allow_data_duplication`](./api/atoti.distribution.query_cube_definition#atoti.QueryCubeDefinition.allow_data_duplication) and [`atoti.Session.create_cube()`](./api/atoti.Session.create_cube#atoti.Session.create_cube)’s *priority* parameter <sup>[66](#python-542)</sup>.
* `atoti.QueryCube.unload_members_from_data_cube` and [`atoti.Session.create_cube()`](./api/atoti.Session.create_cube#atoti.Session.create_cube)’s *id\_in\_cluster* parameter <sup>[65](#python-538)</sup>.
* Support for condition on hierarchy members (not only member paths) in [`atoti.Cube.query()`](./api/atoti.Cube.query#atoti.Cube.query)’s *filter* parameter <sup>[67](#python-554)</sup>.
* [`atoti.date_shift()`](./api/atoti.function.date_shift#atoti.date_shift)’s *dense* parameter <sup>[71](#python-591)</sup>.
* [`experimental()`](./api/atoti.experimental#atoti.experimental) context manager.

### Changed

* Upgraded Atoti UI and Atoti Admin UI to [5.2.8](https://docs.activeviam.com/products/atoti/ui/5.2/docs/changelog/#528).
* Upgraded Atoti Server to [6.1.8](https://docs.activeviam.com/products/atoti/server/6.1.8/docs/release/whats_new/#618).
* The functions that were in the `atoti.experimental` subpackage have been moved outside of it and require passing their feature key to [`experimental()`](./api/atoti.experimental#atoti.experimental):
  ```diff theme={"languages":{"custom":["/engine/python-sdk/0.9/languages/pycon.tmLanguage.json"]}}
  - tt.experimental.agg.distinct(...)
  + with tt.experimental({"agg.distinct"}):
  +   tt.agg.distinct(...)
  ```

### Deprecated

* Deleting a level through [`atoti.Cube.levels`](./api/atoti.Cube.levels#atoti.Cube.levels).
  Redefine its hierarchy instead:
  ```diff theme={"languages":{"custom":["/engine/python-sdk/0.9/languages/pycon.tmLanguage.json"]}}
  - del l["Geography", "City"]
  + h["Geography"] = [l["Continent"], l["Country"]]
  ```
* [`atoti.date_shift()`](./api/atoti.function.date_shift#atoti.date_shift)’s *method* parameter <sup>[71](#python-591)</sup>.
  The new parameter name is *fallback*:
  ```diff theme={"languages":{"custom":["/engine/python-sdk/0.9/languages/pycon.tmLanguage.json"]}}
    tt.date_shift(
      m["Price"],
      h["Date"],
  -   method="previous",
  +   fallback="past",
      offset="P1M",
    )
  ```

### Fixed

* Display of join labels in [`atoti.tables.Tables.schema`](./api/atoti.tables.Tables.schema#atoti.tables.Tables.schema) <sup>[69](#python-584)</sup>.
* [`atoti.where()`](./api/atoti.function.where#atoti.where)’s returned measure evaluating incorrectly when using `|` within another condition <sup>[68](#python-581)</sup>.
* Behavior of [`get()`](https://docs.python.org/3/library/stdtypes.html#dict.get) when setting [`mapping_lookup()`](./api/atoti.mapping_lookup#atoti.mapping_lookup)’s *check* to `False`.
* Missing continuous query updates when using measures such as [`atoti.agg.sum()`](./api/atoti.agg.sum#atoti.agg.sum) with [`CumulativeScope`](./api/atoti.scope.cumulative_scope#atoti.CumulativeScope) <sup>[70](#python-586)</sup>.

### Internal issue tracker references

* <a id="python-538">**\[65]**</a> `PYTHON-538`
* <a id="python-542">**\[66]**</a> `PYTHON-542`
* <a id="python-554">**\[67]**</a> `PYTHON-554`
* <a id="python-581">**\[68]**</a> `PYTHON-581`
* <a id="python-584">**\[69]**</a> `PYTHON-584`
* <a id="python-586">**\[70]**</a> `PYTHON-586`
* <a id="python-591">**\[71]**</a> `PYTHON-591`

## 0.9.5

Released on April 4, 2025.

### Security

#### SSO

The [`atoti.KerberosConfig.username_case_conversion`](./api/atoti.config.security.kerberos_config#atoti.KerberosConfig.username_case_conversion) and [`atoti.LdapConfig.username_case_conversion`](./api/atoti.config.security.ldap_config#atoti.LdapConfig.username_case_conversion) attributes have been added to coerce the name of users logging in to the expected case <sup>[72](#pivot-11110)</sup>.

Not picking a case conversion is a source of confusion or bugs so leaving these attributes unset will raise a deprecation warning.

#### Database access

[`atoti.tables.Tables.owners`](./api/atoti.tables.Tables.owners#atoti.tables.Tables.owners) and [`atoti.tables.Tables.readers`](./api/atoti.tables.Tables.readers#atoti.tables.Tables.readers) have been added <sup>[73](#python-528)</sup>.

Their impact is not limited to the Python API.
For instance, [`atoti.tables.Tables.readers`](./api/atoti.tables.Tables.readers#atoti.tables.Tables.readers) will also control whether end users are able to see tables in Atoti Admin UI Database tab.

### Dependencies

* Upgraded Atoti UI and Atoti Admin UI to [5.2.7](https://docs.activeviam.com/products/atoti/ui/5.2/docs/changelog/#527).
* Upgraded Atoti Server to [6.1.6](https://docs.activeviam.com/products/atoti/server/6.1.6/docs/release/whats_new/#616).

### Fixed

#### Data loading

##### Cloud storage

On Windows, passing a URL to [`atoti.CsvLoad.path`](./api/atoti.data_load.csv_load#atoti.CsvLoad.path) or [`atoti_parquet.ParquetLoad.path`](./api/atoti_parquet.parquet_load#atoti_parquet.ParquetLoad.path) raised an `InvalidPathException` <sup>[77](#python-539)</sup>.

#### Data modeling

##### Conditions

Creating logical conditions (i.e. boolean combinations of leaf conditions such as `(level["Product"] == "Phone") | (level["Country"] == "Portugal")`) with more than 508 leaves raised a `ValidationError` because it reached the maximum nesting depth supported by [the runtime type checker](https://docs.pydantic.dev) <sup>[78](#python-548)</sup>.

This was fixed by allowing the internal representation of a logical condition to group more than 2 operands.
For example, `(a & b) | c | d | f | g` (with a maximum depth of 2) replaces the old internal representation `(((a & b) | c) | (d | f)) | g` (with a maximum depth of 4).

#### Table columns

[`Column`](./api/atoti.column#atoti.Column)s are strictly typed: a column with a `"LocalDate"` [`data_type`](./api/atoti.Column.data_type#atoti.Column.data_type) can only store [`date`](https://docs.python.org/3/library/datetime.html#datetime.date)s (or `None` if its [`default_value`](./api/atoti.Column.default_value#atoti.Column.default_value) is `None`); it cannot store a `"String"` such as `"NaN"`.
Since Java has no equivalent of [`pandas.NaT`](https://pandas.pydata.org/pandas-docs/version/2.3/reference/api/pandas.NaT.html#pandas.NaT), the only available values to represent a special null-restricted:

* `"LocalDate"` are `LocalDate.MIN` and `LocalDate.MAX`,
* `"LocalDateTime"` are `LocalDateTime.MIN` and `LocalDateTime.MAX`.

However, accessing [`default_value`](./api/atoti.Column.default_value#atoti.Column.default_value) when it was set to one of these values raised a `ValidationError` <sup>[74](#python-529)</sup>.
This is fixed.

#### Measures

* The type annotation of [`filter()`](./api/atoti.function.filter#atoti.filter)’s *filter* parameter never allowed inverted [`atoti.Level.isin()`](./api/atoti.Level.isin#atoti.Level.isin) conditions (e.g. `~level.isin("foo", "bar")`) but, by chance, these conditions actually behaved as expected at runtime.

  However, [0.9.4](#id146) introduced runtime validation of condition types which lead to the rejection of these conditions.
  This [regression](https://en.wiktionary.org/wiki/Hyrum%27s_law) is fixed: the type annotation of *filter* accepts these conditions and they are supported at runtime <sup>[75](#python-534)</sup>.
* Passing [`isnull`](./api/atoti.Measure.isnull#atoti.Measure.isnull) conditions to [`atoti.where()`](./api/atoti.function.where#atoti.where) raised an `UnknownUnderlyingMeasureRuntimeException` <sup>[76](#python-536)</sup>.

### Internal issue tracker references

* <a id="pivot-11110">**\[72]**</a> `PIVOT-11110`
* <a id="python-528">**\[73]**</a> `PYTHON-528`
* <a id="python-529">**\[74]**</a> `PYTHON-529`
* <a id="python-534">**\[75]**</a> `PYTHON-534`
* <a id="python-536">**\[76]**</a> `PYTHON-536`
* <a id="python-539">**\[77]**</a> `PYTHON-539`
* <a id="python-548">**\[78]**</a> `PYTHON-548`

## 0.9.4

Released on February 28, 2025.

### Distribution

The name `QuerySession`, unused since [0.9.0](#id171), makes a come back in this release but, this time, with a different meaning.
[`QuerySession`](./api/atoti.distribution.query_session#atoti.QuerySession) becomes the entry point to create clusters of Atoti applications <sup>[84](#python-296)</sup>.

[`atoti.Cube.restrictions`](./api/atoti.Cube.restrictions#atoti.Cube.restrictions) has been introduced to secure query sessions.
For the sake of symmetry, `atoti.Session.security.restrictions` has moved to [`atoti.tables.Tables.restrictions`](./api/atoti.tables.Tables.restrictions#atoti.tables.Tables.restrictions) (the old location remains available but is deprecated).

See [Scaling with distribution](./guides/scaling_with_distribution).

### Performance

#### Client/server communication

Most components of [`Session`](./api/atoti.session#atoti.Session) are exposed through mappings such as [`Cubes`](./api/atoti.cubes#atoti.cubes.Cubes) or [`Tables`](./api/atoti.tables#atoti.tables.Tables).
As a project grows, a lot of mapping lookups will be made to iteratively define the data model or to pass arguments to methods such as [`atoti.Cube.query()`](./api/atoti.Cube.query#atoti.Cube.query).
For instance, the following code will make 3 lookups:

```python theme={"languages":{"custom":["/engine/python-sdk/0.9/languages/pycon.tmLanguage.json"]}}
new_measure = tt.agg.sum(
    m["Foo.SUM"], # 1
    scope=tt.OriginScope({
        l["Bar"], # 2
        l["Baz"], # 3
    })
)
```

Each lookup makes a request to the server to check that the key exists.
This is pretty quick but, when thousands of lookups are made, this client/server communication can add up.
[`mapping_lookup()`](./api/atoti.mapping_lookup#atoti.mapping_lookup) allows skipping these server requests.

#### Cube queries

The aggregate cache of a cube can be restricted to a subset of measures <sup>[81](#pivot-10678)</sup>.
This improvement comes with a new [`atoti.Cube.aggregate_cache`](./api/atoti.Cube.aggregate_cache#atoti.Cube.aggregate_cache) API deprecating the previous `atoti.Cube.aggregates_cache` (with an “s”) one:

```diff theme={"languages":{"custom":["/engine/python-sdk/0.9/languages/pycon.tmLanguage.json"]}}
- cube.aggregates_cache.capacity = 200
+ cube.aggregate_cache = tt.AggregateCache(capacity=200)
- cube.aggregates_cache.capacity = -1
+ del cube.aggregate_cache
```

#### Hierarchy creation

Creating hierarchies from columns of a table fully joined (i.e. all their keys are mapped) to the cube’s fact table do not require “rebuilding” the cube anymore <sup>[82](#pivot-10995)</sup>.
This means that no time will be lost reindexing hierarchies or performing other expensive computations.

### Data modeling

#### Measures

[`atoti.where()`](./api/atoti.function.where#atoti.where) was not respecting the order of *condition\_to\_value* when the same value was assigned to multiple conditions <sup>[80](#pivot-10571)</sup>.
For example, in:

```python theme={"languages":{"custom":["/engine/python-sdk/0.9/languages/pycon.tmLanguage.json"]}}
m["8"] = 8
m["Test"] = tt.where(
    {
        m["8"] < 5: -1, # False
        m["8"] >= 5: 1, # True
        m["8"] < 10: -1, # True
    },
    default=0,
)
```

Test was equal to `-1` because the first and last conditions, being both assigned to the same value, were merged together and that merged condition became the first one evaluating to `True`.
This incorrect merging of conditions has been removed: `m["8"] >= 5` is correctly detected as the first condition evaluating to `True` and so Test is equal to `1`.

#### Table columns

Columns with an arrray data type can be made non-nullable.
[`atoti.Column.default_value`](./api/atoti.Column.default_value#atoti.Column.default_value)’s documentation has been updated accordingly.

### Data loading

#### Transactions

[`data_transaction()`](./api/atoti.tables.Tables.data_transaction#atoti.tables.Tables.data_transaction)’s *tables* parameter allows some data transactions to execute concurrently <sup>[79](#pivot-10472)</sup>.

#### CSV

The [`atoti.CsvLoad.true_values`](./api/atoti.data_load.csv_load#atoti.CsvLoad.true_values) and [`atoti.CsvLoad.false_values`](./api/atoti.data_load.csv_load#atoti.CsvLoad.false_values) attributes can be configured to parse more values than `"True"`, `"true"`, `"False"`, and `"false"` as `"boolean"` <sup>[83](#python-190)</sup>.

### Dependencies

* Atoti Server has been upgraded to [6.1.4](https://docs.activeviam.com/products/atoti/server/6.1.4/docs/release/whats_new/#614).
* Atoti UI and Atoti Admin UI have been upgraded to [5.2.6](https://docs.activeviam.com/products/atoti/ui/5.2/docs/changelog/#526).

### Internal issue tracker references

* <a id="pivot-10472">**\[79]**</a> `PIVOT-10472`
* <a id="pivot-10571">**\[80]**</a> `PIVOT-10571`
* <a id="pivot-10678">**\[81]**</a> `PIVOT-10678`
* <a id="pivot-10995">**\[82]**</a> `PIVOT-10995`
* <a id="python-190">**\[83]**</a> `PYTHON-190`
* <a id="python-296">**\[84]**</a> `PYTHON-296`

## 0.9.3

Released on January 13, 2025.

### Added

* Support for Python 3.13.
* Support for [`Measure`](./api/atoti.measure#atoti.Measure) conditions in [`query()`](./api/atoti.Cube.query#atoti.Cube.query).

### Changed

* Upgraded Atoti Server to [6.1.3](https://docs.activeviam.com/products/atoti/server/6.1.3/docs/release/changelog/#613).
* Upgraded Atoti UI and Atoti Admin UI to [5.2.4](https://docs.activeviam.com/products/atoti/ui/5.2/docs/changelog/#524).
* [`SessionConfig`](./api/atoti.config.session_config#atoti.SessionConfig) and its inner classes validate that passed [`Path`](https://docs.python.org/3/library/pathlib.html#pathlib.Path) attributes exist.
  It prevents situations such as passing a path containing a typo to [`extra_jars`](./api/atoti.config.session_config#atoti.SessionConfig.extra_jars) and being confused that the JAR’s classes still cannot be loaded.

### Fixed

* `Session.read_parquet` and [`ParquetLoad`](./api/atoti_parquet.parquet_load#atoti_parquet.ParquetLoad) not being able to load Parquet files with extension other than `.parquet`.
* `NullPointerException` returned by the server when executing some MDX queries.

## 0.9.2

Released on December 05, 2024.

### Added

* [`infer_data_types()`](./api/atoti.tables.Tables.infer_data_types#atoti.tables.Tables.infer_data_types), [`load()`](./api/atoti.Table.load#atoti.Table.load), and [`load_async()`](./api/atoti.Table.load_async#atoti.Table.load_async) methods.
  Combined with the existing [`create_table()`](./api/atoti.Session.create_table#atoti.Session.create_table) and [`data_transaction()`](./api/atoti.tables.Tables.data_transaction#atoti.tables.Tables.data_transaction) methods, they form the preferred way to create tables and load data into them.
* [`atoti_jdbc.JdbcLoad.parameters`](./api/atoti_jdbc.jdbc_load#atoti_jdbc.JdbcLoad.parameters) to bind variables to queries and avoid SQL injection.
* Runtime check that [`atoti.OidcConfig.scopes`](./api/atoti.config.security.oidc_config#atoti.OidcConfig.scopes) includes the required `"openid"` scope.

#### Documentation

* Pressing the `y` key after clicking on an anchor in a page under [https://docs.activeviam.com/products/atoti/python-sdk/latest/](https://docs.activeviam.com/products/atoti/python-sdk/latest/) will permalink the URL by replacing latest with the corresponding version.

### Changed

* Upgraded Atoti Server to [6.1.2](https://docs.activeviam.com/products/atoti/server/6.1.2/docs/release/changelog/#612).
* Upgraded Atoti UI and Atoti Admin UI to [5.2.3](https://docs.activeviam.com/products/atoti/ui/5.2/docs/changelog/#523).
* To support remote clusters, `atoti.Session.read_spark()` and `atoti.Table.load_spark()` convert the passed Spark DataFrame to an in-memory Pandas DataFrame instead of exporting it to a Parquet file.

### Deprecated

* `atoti.Table.append()`:
  ```diff theme={"languages":{"custom":["/engine/python-sdk/0.9/languages/pycon.tmLanguage.json"]}}
  - table.append(*rows)
  + table.load(pd.DataFrame(rows, columns=list(table)))
  ```
* `atoti.Table.load_arrow()`:
  ```diff theme={"languages":{"custom":["/engine/python-sdk/0.9/languages/pycon.tmLanguage.json"]}}
  - table.load_arrow(arrow_table)
  + table.load(arrow_table)
  ```
* `atoti.Table.load_csv()`:
  ```diff theme={"languages":{"custom":["/engine/python-sdk/0.9/languages/pycon.tmLanguage.json"]}}
  - table.load_csv(path)
  + table.load(tt.CsvLoad(path))
  ```
* `atoti.Table.load_kafka()`:
  ```diff theme={"languages":{"custom":["/engine/python-sdk/0.9/languages/pycon.tmLanguage.json"]}}
  - table.load_kafka(...)
  + from atoti_kafka import KafkaStream
  + table.stream(KafkaStream(...))
  ```
* `atoti.Table.load_numpy()`:
  ```diff theme={"languages":{"custom":["/engine/python-sdk/0.9/languages/pycon.tmLanguage.json"]}}
  - table.load_numpy(numpy_array)
  + table.load(pd.DataFrame(numpy_array, columns=list(table)))
  ```
* `atoti.Table.load_pandas()`:
  ```diff theme={"languages":{"custom":["/engine/python-sdk/0.9/languages/pycon.tmLanguage.json"]}}
  - table.load_pandas(pandas_df)
  + table.load(pandas_df)
  ```
* `atoti.Table.load_parquet()`:
  ```diff theme={"languages":{"custom":["/engine/python-sdk/0.9/languages/pycon.tmLanguage.json"]}}
  - table.load_parquet(path)
  + table.load(atoti_parquet.ParquetLoad(path))
  ```
* `atoti.Table.load_spark()`
  ```diff theme={"languages":{"custom":["/engine/python-sdk/0.9/languages/pycon.tmLanguage.json"]}}
  - table.load_spark(spark_df)
  + table.load(spark_df.toPandas())
  ```
* `atoti.Table.load_sql()`:
  ```diff theme={"languages":{"custom":["/engine/python-sdk/0.9/languages/pycon.tmLanguage.json"]}}
  - table.load_sql(query, url=url)
  + from atoti_jdbc import JdbcLoad
  + table.load(JdbcLoad(query, url=url))
  ```
* `atoti.Session.read_numpy()`:
  ```diff theme={"languages":{"custom":["/engine/python-sdk/0.9/languages/pycon.tmLanguage.json"]}}
  - table = session.read_numpy(numpy_array, columns=columns)
  + table = session.read_pandas(pd.DataFrame(numpy_array, columns=columns))
  ```
* `atoti.Session.read_spark()`:
  ```diff theme={"languages":{"custom":["/engine/python-sdk/0.9/languages/pycon.tmLanguage.json"]}}
  - table = session.read_spark(spark_df)
  + table = session.read_pandas(spark_df.toPandas())
  ```
* `atoti.Session.read_sql()`:
  ```diff theme={"languages":{"custom":["/engine/python-sdk/0.9/languages/pycon.tmLanguage.json"]}}
  - table = session.read_sql(query, table_name=table_name, url=url)
  + from atoti_jdbc import JdbcLoad
  + jdbc_load = JdbcLoad(query, url=url)
  + data_types = session.tables.infer_data_types(jdbc_load)
  + table = session.create_table(table_name, data_types=data_types)
  + table.load(jdbc_load)
  ```
* [`create_table()`](./api/atoti.Session.create_table#atoti.Session.create_table), [`read_arrow()`](./api/atoti.Session.read_arrow#atoti.Session.read_arrow), [`read_csv()`](./api/atoti.Session.read_csv#atoti.Session.read_csv), [`read_pandas()`](./api/atoti.Session.read_pandas#atoti.Session.read_pandas), `Session.read_parquet`’s *types* parameter.
  Use the *data\_types* parameter instead.
* Inference of [`read_csv()`](./api/atoti.Session.read_csv#atoti.Session.read_csv) and `Session.read_parquet`’s *table\_name* parameter.
  Pass a *table\_name* argument instead.

### Fixed

* [`RecursionError`](https://docs.python.org/3/library/exceptions.html#RecursionError) being raised when passing more than 296 *cases* to [`where()`](./api/atoti.function.where#atoti.where).
* [`KeyError`](https://docs.python.org/3/library/exceptions.html#KeyError) being raised when using a [`Level`](./api/atoti.level#atoti.Level) as a key in [`switch()`](./api/atoti.function.switch#atoti.switch)’s *cases* parameter.
* Snapshotting of [`widget`](./api/atoti.Session.widget#atoti.Session.widget) in JupyterLab >= 4.2 by setting [windowingMode](https://jupyterlab.readthedocs.io/en/4.3.x/getting_started/changelog.html#full-notebook-windowing-mode-by-default) to `"defer"`.

## 0.9.1

Released on October 18, 2024.

### Added

* Support for *Live extension* to [`atoti.Session.connect()`](./api/atoti.Session.connect#atoti.Session.connect).

### Changed

* Upgraded Atoti Server to [6.1.1](https://docs.activeviam.com/products/atoti/server/6.1.1/docs/release/changelog/#611).
* Upgraded Atoti UI and Atoti Admin UI to [5.2.1](https://docs.activeviam.com/products/atoti/ui/5.2/docs/changelog/#521).

### Fixed

* Incorrect use of credentials when reading from S3 in [`atoti-storage-aws`](./api/atoti_storage_aws#module-atoti_storage_aws).
* [`atoti.Cube.query()`](./api/atoti.Cube.query#atoti.Cube.query)’s *include\_totals* not including sub totals of multilevel slicing hierarchies.

## 0.9.0

Released on September 13, 2024.

### Added

* [`atoti-directquery-jdbc`](./api/atoti_directquery_jdbc#module-atoti_directquery_jdbc) to connect to an external database through JDBC.
* [`data_model_transaction()`](./api/atoti.Session.data_model_transaction#atoti.Session.data_model_transaction).
  Batching measure creation with a data model transaction has the same performance as using `Measures.update()` without being limited to independent measures:

  ```diff theme={"languages":{"custom":["/engine/python-sdk/0.9/languages/pycon.tmLanguage.json"]}}
  - m.update({"foo": 13, "bar": 42})
  - m.update({"foo + 1": m["foo"] + 1, "bar + 1": m["bar"] + 1})
  + with session.data_model_transaction():
  +    m["foo"] = 13
  +    m["foo + 1"] = m["foo"] + 1
  +    m["bar"] = 42
  +    m["bar + 1"] = m["bar"] + 1
  ```

  Data model transactions also replace the private API relying on `atoti.MeasureMetadata`:

  ```diff theme={"languages":{"custom":["/engine/python-sdk/0.9/languages/pycon.tmLanguage.json"]}}
  - m["foo"] = (13, tt.MeasureMetadata(visible=True))
  - m["bar"] = (42, tt.MeasureMetadata(description="The answer"))
  + with session.data_model_transaction():
  +    m["foo"] = 13
  +    m["foo"].visible = True
  +    m["bar"] = 42
  +    m["bar"].description = "The answer"
  ```
* [`atoti_directquery_redshift.ConnectionConfig.connection_pool_size`](./api/atoti_directquery_redshift.connection_config#atoti_directquery_redshift.ConnectionConfig.connection_pool_size).

#### User interface

* Filters tool in the sidebar of the JupyterLab extension to see default filters.

### Changed

#### Packaging

* The `atoti` package and most of its plugins (e.g. `atoti-aws`, `atoti-directquery-directquery`, `atoti-kafka` etc.) have been split into `atoti-client-*` and `atoti-server-*` packages.
  The `atoti-client-*` packages contain the Python code composing the API while the `atoti-server-*` packages mostly contain the JARs implementing the corresponding features.
  The [advanced installation](./getting_started/installation#advanced) section explains the goal of this split.

  The `atoti` package still exists but has become empty, it is only there to provide a convenient way to install both client and server packages.
  For instance:

  * `pip install atoti` will install both `atoti-client` and `atoti-server` .
    It will actually also install `jdk4py`.

    *Note*: `jdk4py` is not a dependency of `atoti-server` so that projects willing to use another JDK can avoid installing `jdk4py` by dependending on `atoti-client` and `atoti-server` directly.
  * `pip install "atoti[aws]"` will install `atoti-client`, `atoti-aws-client`, `atoti-aws-server`, and `atoti-server`.
  * `pip install "atoti[jupyterlab]"` will install `atoti-client`, `atoti-server`, and `atoti-jupyterlab` (no client/server split for this package because it only contains frontend assets).

  Because Conda does not support “extras”, the installation of Atoti plugins with this package manager is more complex.
  For instance, the command to install `atoti` and its AWS plugin is: `conda install atoti atoti-client-aws atoti-server-aws`.

#### Session start and configuration

* `atoti.Session.__init__()` has been replaced with [`atoti.Session.start()`](./api/atoti.Session.start#atoti.Session.start) for symmetry with [`atoti.Session.connect()`](./api/atoti.Session.connect#atoti.Session.connect) (the [removed](#changelog-0-9-0-removed) section gives more details about that latter method):

  ```diff theme={"languages":{"custom":["/engine/python-sdk/0.9/languages/pycon.tmLanguage.json"]}}
  - session = tt.Session()
  + session = tt.Session.start()
  ```

  The top-level config parameters have been grouped into a [`SessionConfig`](./api/atoti.config.session_config#atoti.SessionConfig) dataclass providing better error reporting and allowing code reuse:

  ```diff theme={"languages":{"custom":["/engine/python-sdk/0.9/languages/pycon.tmLanguage.json"]}}
  - session = tt.Session(port=1337)
  + session = tt.Session.start(tt.SessionConfig(port=1337))
  ```
* `atoti.Session.__init__()`’s *authentication* parameter has been replaced with [`atoti.SessionConfig.security`](./api/atoti.config.session_config#atoti.SessionConfig.security):
  ```diff theme={"languages":{"custom":["/engine/python-sdk/0.9/languages/pycon.tmLanguage.json"]}}
    config = tt.OidcConfig(...)
  - tt.Session(authentication=config)
  + tt.Session.start(tt.SessionConfig(security=tt.SecurityConfig(sso=config)))
  ```
* `atoti.UserContentStorageConfig` has been moved to [`atoti_jdbc.UserContentStorageConfig`](./api/atoti_jdbc.user_content_storage_config#atoti_jdbc.UserContentStorageConfig):

  ```diff theme={"languages":{"custom":["/engine/python-sdk/0.9/languages/pycon.tmLanguage.json"]}}
  - config = tt.UserContentStorageConfig(url=url)
  - tt.Session(user_content_storage=config)
  + from atoti_jdbc import UserContentStorageConfig
  + config = UserContentStorageConfig(url)
  + tt.Session.start(tt.SessionConfig(user_content_storage=config))
  ```

  It makes it obvious that storing user content in an external database requires [`atoti-jdbc`](./api/atoti_jdbc#module-atoti_jdbc) to be installed.

#### DirectQuery

* DirectQuery `*ConnectionInfo` and `*TableOptions` classes have been renamed `ConnectionConfig` and `TableConfig`.

  ```diff theme={"languages":{"custom":["/engine/python-sdk/0.9/languages/pycon.tmLanguage.json"]}}
  - from atoti_directquery_clickhouse import ClickhouseConnectionInfo
  + from atoti_directquery_clickhouse import ConnectionConfig
  ```

  ```diff theme={"languages":{"custom":["/engine/python-sdk/0.9/languages/pycon.tmLanguage.json"]}}
  - from atoti_directquery_clickhouse import ClickhouseTableOptions
  + from atoti_directquery_clickhouse import TableConfig
  ```
* The `cache` attribute controlling whether DirectQuery connections should use caching has been moved from the connection instance to the connection config:
  ```diff theme={"languages":{"custom":["/engine/python-sdk/0.9/languages/pycon.tmLanguage.json"]}}
  - from atoti_directquery_snowflake import SnowflakeConnectionInfo
  + from atoti_directquery_snowflake import ConnectionConfig
  - connection_config = SnowflakeConnectionInfo(url=...)
  + connection_config = ConnectionConfig(url=..., cache=True)
    external_database = session.connect_to_external_database(connection_config)
  - external_database.cache = True
  ```
* The `DatabricksConnectionInfo.heavy_load_url` attribute has been renamed [`feeding_url`](./api/atoti_directquery_databricks.connection_config#atoti_directquery_databricks.ConnectionConfig.feeding_url).

#### Other

* Upgraded `jdk4py` dependency to 21.0.4 which adds support for Linux Arm64.
* `atoti_aws.AwsKeyPair`, `atoti_aws.AwsKmsConfig`, and `atoti_azure.AzureKeyPair` have been renamed [`KeyPair`](./api/atoti_storage_aws.client_side_encryption.key_pair#atoti_storage_aws.KeyPair), [`KmsConfig`](./api/atoti_storage_aws.client_side_encryption.kms_config#atoti_storage_aws.KmsConfig), and [`KeyPair`](./api/atoti_storage_azure.client_side_encryption.key_pair#atoti_storage_azure.KeyPair).
* The `atoti-sql` package has been renamed [`atoti-jdbc`](./api/atoti_jdbc#module-atoti_jdbc).
* `atoti.Session.explain_mdx_query()` and `atoti.Cube.explain_query()` have been replaced with an *explain* parameter to [`atoti.Session.query_mdx()`](./api/atoti.Session.query_mdx#atoti.Session.query_mdx) and [`atoti.Cube.query()`](./api/atoti.Cube.query#atoti.Cube.query):
  ```diff theme={"languages":{"custom":["/engine/python-sdk/0.9/languages/pycon.tmLanguage.json"]}}
  - session.explain_mdx_query(mdx)
  + session.query_mdx(mdx, explain=True)
  ```
* [`atoti.Table.keys`](./api/atoti.Table.keys#atoti.Table.keys) returns a [`tuple`](https://docs.python.org/3/library/stdtypes.html#tuple) instead of a [`list`](https://docs.python.org/3/library/stdtypes.html#list).
  It communicates that keys cannot be changed once the table exists.
* [`create_cube()`](./api/atoti.Session.create_cube#atoti.Session.create_cube)’s *base\_table* parameter has been renamed *fact\_table*.

#### User interface

* Upgraded Atoti UI and Admin UI to [5.2.0](https://docs.activeviam.com/products/atoti/ui/5.2/docs/migration/5.1-to-5.2/).

### Deprecated

* `atoti.Session.port`.
  Use [`atoti.Session.url`](./api/atoti.Session.url#atoti.Session.url) instead:

  ```diff theme={"languages":{"custom":["/engine/python-sdk/0.9/languages/pycon.tmLanguage.json"]}}
  - url = f"http://localhost:{session.port}"
  + url = session.url
  ```

  ```diff theme={"languages":{"custom":["/engine/python-sdk/0.9/languages/pycon.tmLanguage.json"]}}
  - port = session.port
  + from urllib.parse import urlparse
  + port = urlparse(session.url).port
  ```
* `atoti.Session.start_transaction()`.
  Use [`atoti.tables.Tables.data_transaction()`](./api/atoti.tables.Tables.data_transaction#atoti.tables.Tables.data_transaction) instead:
  ```diff theme={"languages":{"custom":["/engine/python-sdk/0.9/languages/pycon.tmLanguage.json"]}}
  - with session.start_transaction(): ...
  + with session.tables.data_model_transaction(): ...
  ```
* `atoti.Session.security.basic`.
  Use [`basic_authentication`](./api/atoti.security.Security.basic_authentication#atoti.security.Security.basic_authentication):
  ```diff theme={"languages":{"custom":["/engine/python-sdk/0.9/languages/pycon.tmLanguage.json"]}}
  - session.security.basic.credentials
  + session.security.basic_authentication.credentials
  ```
* `atoti.Table.columns` and `atoti.ExternalTable.columns`.
  Use `list(table)` to list column names and `for column_name in table: ...` to iterate on column names:
  ```diff theme={"languages":{"custom":["/engine/python-sdk/0.9/languages/pycon.tmLanguage.json"]}}
  - column_names = table.columns
  + column_names = list(table)
  ```
* `atoti.Hierarchy.levels`.
  Iterate on the [`Hierarchy`](./api/atoti.hierarchy#atoti.Hierarchy) instead:

  ```diff theme={"languages":{"custom":["/engine/python-sdk/0.9/languages/pycon.tmLanguage.json"]}}
  - level_names = list(h["Geography"].levels)
  + level_names = list(h["Geography"])
  ```

  ```diff theme={"languages":{"custom":["/engine/python-sdk/0.9/languages/pycon.tmLanguage.json"]}}
  - h["Date parts"] = {**h["Date parts"].levels, "Date": table["Date"]}
  + h["Date parts"] = {**h["Date parts"], "Date": table["Date"]}
  ```

<a id="changelog-0-9-0-removed" />

### Removed

* Support for Java 17, 18, 19, and 20.
* The `atoti-query` package and its `QuerySession` class.
  Use [`atoti.Session.connect()`](./api/atoti.Session.connect#atoti.Session.connect) instead:

  ```diff theme={"languages":{"custom":["/engine/python-sdk/0.9/languages/pycon.tmLanguage.json"]}}
  - pip install atoti-query
  + pip install atoti-client
  ```

  ```diff theme={"languages":{"custom":["/engine/python-sdk/0.9/languages/pycon.tmLanguage.json"]}}
  - from atoti_query import QuerySession
  + from atoti as tt
  - existing_session = QuerySession(url)
  + existing_session = tt.Session.connect(url)
    existing_session.query_mdx(...)
  ```
* `atoti.Table.__len__()`.
  It was ambiguous because it could be interpreted as counting either rows or columns.
  Instead, use [`atoti.Table.row_count`](./api/atoti.Table.row_count#atoti.Table.row_count) to count rows and `len(list(table))` to count columns:
  ```diff theme={"languages":{"custom":["/engine/python-sdk/0.9/languages/pycon.tmLanguage.json"]}}
  - row_count = len(table)
  + row_count = table.row_count
  ```

#### Previously deprecated

* Support for Python 3.9.
* Support for pandas 1.
* Support for JupyterLab 3.
* `atoti.UserServiceClient`.
* `atoti.Cube.schema`.
* Support for passing an [`int`](https://docs.python.org/3/library/functions.html#int) to `atoti.Table.load_kafka()`’s *batch\_duration* parameter.
* Support for passing a [`tuple`](https://docs.python.org/3/library/stdtypes.html#tuple) as a case key to [`switch()`](./api/atoti.function.switch#atoti.switch).
* Variadic constructor of [`OriginScope`](./api/atoti.scope.origin_scope#atoti.OriginScope).
* Support for calling `atoti.Session.link()`.
* `atoti.Session.visualize()` and `atoti.QuerySession.visualize()`.
* Support for array constants mixing [`int`](https://docs.python.org/3/library/functions.html#int) and [`float`](https://docs.python.org/3/library/functions.html#float) values.
* Support for passing a [`Set`](https://docs.python.org/3/library/collections.abc.html#collections.abc.Set) to:
  * `atoti.Session()`’s *extra\_jars* or *java\_options* parameters
  * [`atoti.Cube.query()`](./api/atoti.Cube.query#atoti.Cube.query)’s *levels* parameter
  * [`atoti.Cube.create_parameter_simulation()`](./api/atoti.Cube.create_parameter_simulation#atoti.Cube.create_parameter_simulation)’s *levels* parameter
  * [`atoti.Cube.create_parameter_hierarchy_from_members()`](./api/atoti.Cube.create_parameter_hierarchy_from_members#atoti.Cube.create_parameter_hierarchy_from_members)’s *members* parameter
* Support for passing a [`Sequence`](https://docs.python.org/3/library/collections.abc.html#collections.abc.Sequence) to:
  * DirectQuery `TableConfig` classes’ *clustering\_columns* attribute
  * [`atoti.MultiColumnArrayConversion.column_prefixes`](./api/atoti.directquery.array_conversion.multi_column_array_conversion#atoti.MultiColumnArrayConversion.column_prefixes)
  * [`atoti.MultiRowArrayConversion.array_columns`](./api/atoti.directquery.array_conversion.multi_row_array_conversion#atoti.MultiRowArrayConversion.array_columns)
  * [`atoti.OidcConfig.roles_claims`](./api/atoti.config.security.oidc_config#atoti.OidcConfig.roles_claims)
  * [`atoti.OidcConfig.scopes`](./api/atoti.config.security.oidc_config#atoti.OidcConfig.scopes)
* `atoti.Session.security.users`.
* `atoti.Session.security.basic.users` and `atoti.Session.security.basic.create_user()`.
* `atoti.Session.security.roles` and `atoti.Session.security.create_role()`.
* In-place mutation of `atoti.Security.security.ldap.role_mapping` and `atoti.Security.security.oidc.role_mapping`’s values.
* In-place mutation of `atoti.Security.security.individual_roles`’ values.
