> ## 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.

# 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).

## 0.9.15

Released on May 29, 2026.

### Changed

* Upgraded Atoti Server to [6.1.20](https://docs.activeviam.com/products/atoti/server/6.1/docs/release/whats_new/#6120).
* 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 using its development build when [`__debug__`](https://docs.python.org/3/library/constants.html#debug__) is `True`.
  It is strongly recommended to use [Python’s optimized mode](./getting_started/deployment/deployment_setup#python-optimized-mode) in production to avoid this development build’s overhead.
* Accept using the JDK that the `JAVA_HOME` environment variable points to even if its version is greater than 21.

### Fixed

* [`data_model_transaction()`](./api/atoti.Session.data_model_transaction#atoti.Session.data_model_transaction) raising `IllegalStateException: Structural transaction must be committed by the thread starting it` depending on the order of operations inside it <sup>[1](#python-753)</sup>.
* [`create_cube()`](./api/atoti.Session.create_cube#atoti.Session.create_cube) from an [`external table`](./api/atoti.Session.add_external_table#atoti.Session.add_external_table) with `mode="no_measures"` and a `filter` sending unfiltered `APPROX_COUNT_DISTINCT` queries for [`virtual hierarchy`](./api/atoti.Hierarchy.virtual#atoti.Hierarchy.virtual) detection <sup>[2](#python-869)</sup>.

### Internal issue tracker references

* <a id="python-753">**\[1]**</a> `PYTHON-753`
* <a id="python-869">**\[2]**</a> `PYTHON-869`

## 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`](./api/atoti_directquery_databricks.connection_config#atoti_directquery_databricks.ConnectionConfig.array_sum_agg_function_name), [`array_long_agg_function_name`](./api/atoti_directquery_databricks.connection_config#atoti_directquery_databricks.ConnectionConfig.array_long_agg_function_name), [`array_short_agg_function_name`](./api/atoti_directquery_databricks.connection_config#atoti_directquery_databricks.ConnectionConfig.array_short_agg_function_name), and [`array_sum_product_agg_function_name`](./api/atoti_directquery_databricks.connection_config#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>[4](#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>[3](#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">**\[3]**</a> `PYTHON-798`
* <a id="python-817">**\[4]**</a> `PYTHON-817`

## 0.9.13

Released on March 10, 2026.

### Added

* [`shift()`](./api/atoti.function.shift#atoti.shift)’s *mode* parameter <sup>[6](#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>[10](#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>[8](#python-774)</sup>, <sup>[9](#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>[5](#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>[7](#python-768)</sup>.

### Internal issue tracker references

* <a id="pivot-12311">**\[5]**</a> `PIVOT-12311`
* <a id="python-703">**\[6]**</a> `PYTHON-703`
* <a id="python-768">**\[7]**</a> `PYTHON-768`
* <a id="python-774">**\[8]**</a> `PYTHON-774`
* <a id="python-775">**\[9]**</a> `PYTHON-775`
* <a id="python-789">**\[10]**</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>[11](#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>[11](#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
  ```
* [`read_parquet()`](./api/atoti.Session.read_parquet#atoti.Session.read_parquet) since support for Apache Parquet will become opt-in <sup>[11](#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>[12](#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>[14](#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>[13](#python-742)</sup>.

### Internal issue tracker references

* <a id="pivot-12663">**\[11]**</a> `PIVOT-12663`
* <a id="python-447">**\[12]**</a> `PYTHON-447`
* <a id="python-742">**\[13]**</a> `PYTHON-742`
* <a id="python-755">**\[14]**</a> `PYTHON-755`

## 0.9.11

Released on December 16, 2025.

### Added

* [`atoti.Hierarchy.description`](./api/atoti.Hierarchy.description#atoti.Hierarchy.description) <sup>[18](#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>[20](#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>[18](#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>[15](#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>[18](#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>[21](#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>[16](#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>[19](#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>[17](#python-728)</sup>.

### Internal issue tracker references

* <a id="python-716">**\[15]**</a> `PYTHON-716`
* <a id="python-717">**\[16]**</a> `PYTHON-717`
* <a id="python-728">**\[17]**</a> `PYTHON-728`
* <a id="python-733">**\[18]**</a> `PYTHON-733`
* <a id="python-737">**\[19]**</a> `PYTHON-737`
* <a id="python-739">**\[20]**</a> `PYTHON-739`
* <a id="python-745">**\[21]**</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>[23](#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](./getting_started/installation#conda-package-discouraged) to recommend Miniforge over Anaconda’s installer to not be tied to Anaconda’s license requirements <sup>[24](#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>[22](#python-590)</sup>.

### Internal issue tracker references

* <a id="python-590">**\[22]**</a> `PYTHON-590`
* <a id="python-712">**\[23]**</a> `PYTHON-712`
* <a id="python-715">**\[24]**</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>[26](#python-697)</sup>.

### Changed

* Improved planning of MDX queries requesting many specific cells of the cube <sup>[25](#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>[27](#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>[28](#python-705)</sup>.

### Internal issue tracker references

* <a id="pivot-12464">**\[25]**</a> `PIVOT-12464`
* <a id="python-697">**\[26]**</a> `PYTHON-697`
* <a id="python-698">**\[27]**</a> `PYTHON-698`
* <a id="python-705">**\[28]**</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>[29](#pivot-12032)</sup>.

### Fixed

* `ArrayIndexOutOfBoundsException` when running some MDX queries <sup>[30](#pivot-12336)</sup>.
* `AssertionError: Output has already been set` when deleting a cube <sup>[32](#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>[31](#python-691)</sup>.

### Internal issue tracker references

* <a id="pivot-12032">**\[29]**</a> `PIVOT-12032`
* <a id="pivot-12336">**\[30]**</a> `PIVOT-12336`
* <a id="python-691">**\[31]**</a> `PYTHON-691`
* <a id="python-696">**\[32]**</a> `PYTHON-696`

## 0.9.9

Released on September 22, 2025.

### Added

* [`plugin_measure()`](./api/atoti.function.plugin_measure#atoti.plugin_measure) <sup>[38](#python-634)</sup>.
* [`atoti.Session.client`](./api/atoti.Session.client#atoti.Session.client) <sup>[36](#python-629)</sup>.
* [`Spring Boot Admin plugin`](./api/atoti_spring_boot_admin#module-atoti_spring_boot_admin) <sup>[33](#pivot-12095)</sup>.
* [`atoti_directquery_snowflake.ConnectionConfig.main_warehouse_name`](./api/atoti_directquery_snowflake.connection_config#atoti_directquery_snowflake.ConnectionConfig.main_warehouse_name) <sup>[34](#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>[35](#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.1/docs/mdx/engine_configuration/#light-crossjoin) <sup>[37](#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>[39](#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>[40](#python-678)</sup>.

### Internal issue tracker references

* <a id="pivot-12095">**\[33]**</a> `PIVOT-12095`
* <a id="python-592">**\[34]**</a> `PYTHON-592`
* <a id="python-621">**\[35]**</a> `PYTHON-621`
* <a id="python-629">**\[36]**</a> `PYTHON-629`
* <a id="python-632">**\[37]**</a> `PYTHON-632`
* <a id="python-634">**\[38]**</a> `PYTHON-634`
* <a id="python-677">**\[39]**</a> `PYTHON-677`
* <a id="python-678">**\[40]**</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>[41](#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>[41](#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>[41](#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>[42](#python-650)</sup>.

### Internal issue tracker references

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

## 0.9.7

Released on July 01, 2025.

### Added

* [`atoti.Hierarchy.viewers`](./api/atoti.Hierarchy.viewers#atoti.Hierarchy.viewers) <sup>[45](#python-552)</sup>.
* [`atoti.Hierarchy.folder`](./api/atoti.Hierarchy.folder#atoti.Hierarchy.folder) <sup>[46](#python-593)</sup>.
* [`atoti.SecurityConfig.cors`](./api/atoti.config.security.security_config#atoti.SecurityConfig.cors) <sup>[44](#pivot-11652)</sup>.
* [`atoti.finance.irr()`](./api/atoti.finance.irr#atoti.finance.irr)’s *period* and *guess* parameters <sup>[49](#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>[48](#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>[43](#pivot-11612)</sup>.

### Deprecated

* [`atoti.Hierarchy.visible`](./api/atoti.Hierarchy.visible#atoti.Hierarchy.visible) setter <sup>[45](#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>[47](#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>[49](#python-623)</sup>.

### Internal issue tracker references

* <a id="pivot-11612">**\[43]**</a> `PIVOT-11612`
* <a id="pivot-11652">**\[44]**</a> `PIVOT-11652`
* <a id="python-552">**\[45]**</a> `PYTHON-552`
* <a id="python-593">**\[46]**</a> `PYTHON-593`
* <a id="python-610">**\[47]**</a> `PYTHON-610`
* <a id="python-615">**\[48]**</a> `PYTHON-615`
* <a id="python-623">**\[49]**</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>[51](#python-542)</sup>.
* [`unload_members_from_data_cube()`](./api/atoti.QueryCube.unload_members_from_data_cube#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>[50](#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>[52](#python-554)</sup>.
* [`atoti.date_shift()`](./api/atoti.function.date_shift#atoti.date_shift)’s *dense* parameter <sup>[56](#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>[56](#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>[54](#python-584)</sup>.
* [`atoti.where()`](./api/atoti.function.where#atoti.where)’s returned measure evaluating incorrectly when using `|` within another condition <sup>[53](#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>[55](#python-586)</sup>.

### Internal issue tracker references

* <a id="python-538">**\[50]**</a> `PYTHON-538`
* <a id="python-542">**\[51]**</a> `PYTHON-542`
* <a id="python-554">**\[52]**</a> `PYTHON-554`
* <a id="python-581">**\[53]**</a> `PYTHON-581`
* <a id="python-584">**\[54]**</a> `PYTHON-584`
* <a id="python-586">**\[55]**</a> `PYTHON-586`
* <a id="python-591">**\[56]**</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>[57](#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>[58](#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>[62](#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>[63](#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>[59](#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](#id123) 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>[60](#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>[61](#python-536)</sup>.

### Internal issue tracker references

* <a id="pivot-11110">**\[57]**</a> `PIVOT-11110`
* <a id="python-528">**\[58]**</a> `PYTHON-528`
* <a id="python-529">**\[59]**</a> `PYTHON-529`
* <a id="python-534">**\[60]**</a> `PYTHON-534`
* <a id="python-536">**\[61]**</a> `PYTHON-536`
* <a id="python-539">**\[62]**</a> `PYTHON-539`
* <a id="python-548">**\[63]**</a> `PYTHON-548`

## 0.9.4

Released on February 28, 2025.

### Distribution

The name `QuerySession`, unused since [0.9.0](#id148), 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>[69](#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>[66](#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>[67](#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>[65](#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>[64](#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>[68](#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">**\[64]**</a> `PIVOT-10472`
* <a id="pivot-10571">**\[65]**</a> `PIVOT-10571`
* <a id="pivot-10678">**\[66]**</a> `PIVOT-10678`
* <a id="pivot-10995">**\[67]**</a> `PIVOT-10995`
* <a id="python-190">**\[68]**</a> `PYTHON-190`
* <a id="python-296">**\[69]**</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

* [`read_parquet()`](./api/atoti.Session.read_parquet#atoti.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), [`read_parquet()`](./api/atoti.Session.read_parquet#atoti.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 [`read_parquet()`](./api/atoti.Session.read_parquet#atoti.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.
