Skip to main content

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.

This is a detailed list of all changes. For a higher level view, see the Release notes. Atoti Python SDK implements Atoti version policy in a specific manner to follow Semantic Versioning.

0.9.15

Released on May 29, 2026.

Changed

  • Upgraded Atoti Server to 6.1.20.
  • Upgraded Atoti UI and Atoti Admin UI to 5.2.24.
  • The Atoti UI app is served using its development build when __debug__ is True. It is strongly recommended to use Python’s 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

Internal issue tracker references

  • [1] PYTHON-753
  • [2] 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 for example traces.

Changed

  • Upgraded Atoti Server to 6.1.19.

Deprecated

Fixed

Internal issue tracker references

  • [3] PYTHON-798
  • [4] PYTHON-817

0.9.13

Released on March 10, 2026.

Added

Changed

  • Upgraded Atoti Server to 6.1.18.
  • Upgraded Atoti UI and Atoti Admin UI to 5.2.20.
  • Renamed cloud storage packages:
    • atoti-client-awsatoti-client-storage-aws
    • atoti-server-awsatoti-server-storage-aws
    • atoti-client-azureatoti-client-storage-azure
    • atoti-server-azureatoti-server-storage-azure
    • atoti-client-gcpatoti-client-storage-gcp
    • atoti-server-gcpatoti-server-storage-gcp
    The module names have also changed:
    - uv add "atoti[aws]"
    + uv add "atoti[storage-aws]"
    
    - 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 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 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 instead.

Fixed

Internal issue tracker references

0.9.12

Released on January 23, 2026.

Added

Changed

  • Upgraded Atoti Server to 6.1.16.
  • Upgraded Atoti UI and Atoti Admin UI to 5.2.18.
  • Loading Apache Parquet files requires the new atoti-client-parquet and atoti-server-parquet packages 11. Installing the atoti package also installs these packages but the next breaking release will make them opt-in.
    - 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:
    - # 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:
    - uv add "atoti[jdbc]"
    + uv add "atoti-client[jdbc]" "atoti-server[jdbc]"
    

Deprecated

  • atoti.ParquetLoad 11. Use atoti_parquet.ParquetLoad instead:
    - from atoti import ParquetLoad
    + from atoti_parquet import ParquetLoad
    
  • read_parquet() since support for Apache Parquet will become opt-in 11. Use infer_data_types(), create_table(), and load() instead:
    - 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() and atoti.Cube.query()’s timeout parameter 12. The default value has also been changed from datetime.timedelta(seconds=30) to None to automatically use shared_context’s queriesTimeLimit . Use the context parameter to specify a custom timeout:
    - session.query_mdx(mdx, timeout=datetime.timedelta(seconds=10))
    + session.query_mdx(mdx, context={"queriesTimeLimit": 10})
    
    - 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 14.
  • Hierarchies disappearing from the Data model tree when editing the data model of a secured session 13.

Internal issue tracker references

0.9.11

Released on December 16, 2025.

Added

Changed

  • Upgraded Atoti Server to 6.1.15.
  • Upgraded Atoti UI and Atoti Admin UI to 5.2.17.
  • atoti.Measure.description returns "" instead of None 18.
  • Everything related to Security is kept in memory instead of being persisted in the user_content_storage 15. This makes the API more consistent since all the other methods on atoti.Session and its related classes never persisted their changes either. This improves performance, both when configuring these properties but also when executing queries.
    Basic authentication credentials are unaffected since, for security reasons, they were already only kept in memory.

Deprecated

  • atoti.Measure.description’s deleter 18. Set the description to a blank string instead:
    - del measure.description
    + measure.description = ""
    

Fixed

Internal issue tracker references

0.9.10

Released on October 31, 2025.

Added

Changed

  • Upgraded Atoti Server to 6.1.13.
  • Upgraded Atoti UI and Atoti Admin UI to 5.2.15.
  • Installation instructions with Conda to recommend Miniforge over Anaconda’s installer to not be tied to Anaconda’s license requirements 24.
  • Configuring 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 22.

Internal issue tracker references

0.9.9.2

Released on October 20, 2025.
This version is not published on PyPI, it is only available on ActiveViam’s repository.

Added

Changed

  • Improved planning of MDX queries requesting many specific cells of the cube 25.
  • Reduce number of requests made when representing (or evaluating in a Jupyter notebook cell) cubes, levels, measures, etc of a connected session 27.

Fixed

Internal issue tracker references

0.9.9.1

Released on September 30, 2025.
This version is not published on PyPI, it is only available on ActiveViam’s repository.

Changed

  • Improved planning of MDX queries requesting many specific cells of the cube 29.

Fixed

  • ArrayIndexOutOfBoundsException when running some MDX queries 30.
  • AssertionError: Output has already been set when deleting a cube 32.
  • GraphQLClientGraphQLMultiError: Structural transaction must be committed by the thread starting it when setting atoti.Measure.visible inside a data_model_transaction() 31.

Internal issue tracker references

0.9.9

Released on September 22, 2025.

Added

Changed

Fixed

Internal issue tracker references

0.9.8

Released on August 12, 2025.

Added

Changed

  • Upgraded Atoti Server to 6.1.11.
  • When 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 should now use /proxy instead of /atoti/pyapi. /atoti/pyapi remains available but logs a warning when used 41.
  • The atoti.pyapi module is deprecated, import its classes directly from atoti.User or atoti.endpoint.Request instead 41.

Fixed

Internal issue tracker references

0.9.7

Released on July 01, 2025.

Added

Changed

Deprecated

Fixed

  • where() rejecting condition if it contained a ~hierarchy.isin(...) or ~level.isin(...) leaf 47.
  • atoti.finance.irr() returning nan when the actual rate was too large 49.

Internal issue tracker references

0.9.6

Released on May 17, 2025.

Added

Changed

  • Upgraded Atoti UI and Atoti Admin UI to 5.2.8.
  • Upgraded Atoti Server to 6.1.8.
  • The functions that were in the atoti.experimental subpackage have been moved outside of it and require passing their feature key to experimental():
    - tt.experimental.agg.distinct(...)
    + with tt.experimental({"agg.distinct"}):
    +   tt.agg.distinct(...)
    

Deprecated

  • Deleting a level through atoti.Cube.levels. Redefine its hierarchy instead:
    - del l["Geography", "City"]
    + h["Geography"] = [l["Continent"], l["Country"]]
    
  • atoti.date_shift()’s method parameter 56. The new parameter name is fallback:
      tt.date_shift(
        m["Price"],
        h["Date"],
    -   method="previous",
    +   fallback="past",
        offset="P1M",
      )
    

Fixed

Internal issue tracker references

0.9.5

Released on April 4, 2025.

Security

SSO

The atoti.KerberosConfig.username_case_conversion and atoti.LdapConfig.username_case_conversion attributes have been added to coerce the name of users logging in to the expected case 57. 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 and atoti.tables.Tables.readers have been added 58. Their impact is not limited to the Python API. For instance, 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.
  • Upgraded Atoti Server to 6.1.6.

Fixed

Data loading

Cloud storage
On Windows, passing a URL to atoti.CsvLoad.path or atoti_parquet.ParquetLoad.path raised an InvalidPathException 62.

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

Columns are strictly typed: a column with a "LocalDate" data_type can only store dates (or None if its default_value is None); it cannot store a "String" such as "NaN". Since Java has no equivalent of 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 when it was set to one of these values raised a ValidationError 59. This is fixed.

Measures

  • The type annotation of filter()’s filter parameter never allowed inverted 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 introduced runtime validation of condition types which lead to the rejection of these conditions. This regression is fixed: the type annotation of filter accepts these conditions and they are supported at runtime 60.
  • Passing isnull conditions to atoti.where() raised an UnknownUnderlyingMeasureRuntimeException 61.

Internal issue tracker references

0.9.4

Released on February 28, 2025.

Distribution

The name QuerySession, unused since 0.9.0, makes a come back in this release but, this time, with a different meaning. QuerySession becomes the entry point to create clusters of Atoti applications 69. 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 (the old location remains available but is deprecated). See Scaling with distribution.

Performance

Client/server communication

Most components of Session are exposed through mappings such as Cubes or 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(). For instance, the following code will make 3 lookups:
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() allows skipping these server requests.

Cube queries

The aggregate cache of a cube can be restricted to a subset of measures 66. This improvement comes with a new atoti.Cube.aggregate_cache API deprecating the previous atoti.Cube.aggregates_cache (with an “s”) one:
- 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 67. This means that no time will be lost reindexing hierarchies or performing other expensive computations.

Data modeling

Measures

atoti.where() was not respecting the order of condition_to_value when the same value was assigned to multiple conditions 65. For example, in:
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’s documentation has been updated accordingly.

Data loading

Transactions

data_transaction()’s tables parameter allows some data transactions to execute concurrently 64.

CSV

The atoti.CsvLoad.true_values and atoti.CsvLoad.false_values attributes can be configured to parse more values than "True", "true", "False", and "false" as "boolean" 68.

Dependencies

  • Atoti Server has been upgraded to 6.1.4.
  • Atoti UI and Atoti Admin UI have been upgraded to 5.2.6.

Internal issue tracker references

0.9.3

Released on January 13, 2025.

Added

Changed

  • Upgraded Atoti Server to 6.1.3.
  • Upgraded Atoti UI and Atoti Admin UI to 5.2.4.
  • SessionConfig and its inner classes validate that passed Path attributes exist. It prevents situations such as passing a path containing a typo to extra_jars and being confused that the JAR’s classes still cannot be loaded.

Fixed

  • read_parquet() and 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

Documentation

Changed

  • Upgraded Atoti Server to 6.1.2.
  • Upgraded Atoti UI and Atoti Admin UI to 5.2.3.
  • 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():
    - table.append(*rows)
    + table.load(pd.DataFrame(rows, columns=list(table)))
    
  • atoti.Table.load_arrow():
    - table.load_arrow(arrow_table)
    + table.load(arrow_table)
    
  • atoti.Table.load_csv():
    - table.load_csv(path)
    + table.load(tt.CsvLoad(path))
    
  • atoti.Table.load_kafka():
    - table.load_kafka(...)
    + from atoti_kafka import KafkaStream
    + table.stream(KafkaStream(...))
    
  • atoti.Table.load_numpy():
    - table.load_numpy(numpy_array)
    + table.load(pd.DataFrame(numpy_array, columns=list(table)))
    
  • atoti.Table.load_pandas():
    - table.load_pandas(pandas_df)
    + table.load(pandas_df)
    
  • atoti.Table.load_parquet():
    - table.load_parquet(path)
    + table.load(atoti_parquet.ParquetLoad(path))
    
  • atoti.Table.load_spark()
    - table.load_spark(spark_df)
    + table.load(spark_df.toPandas())
    
  • atoti.Table.load_sql():
    - table.load_sql(query, url=url)
    + from atoti_jdbc import JdbcLoad
    + table.load(JdbcLoad(query, url=url))
    
  • atoti.Session.read_numpy():
    - table = session.read_numpy(numpy_array, columns=columns)
    + table = session.read_pandas(pd.DataFrame(numpy_array, columns=columns))
    
  • atoti.Session.read_spark():
    - table = session.read_spark(spark_df)
    + table = session.read_pandas(spark_df.toPandas())
    
  • atoti.Session.read_sql():
    - 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(), read_arrow(), read_csv(), read_pandas(), read_parquet()’s types parameter. Use the data_types parameter instead.
  • Inference of read_csv() and read_parquet()’s table_name parameter. Pass a table_name argument instead.

Fixed

0.9.1

Released on October 18, 2024.

Added

Changed

  • Upgraded Atoti Server to 6.1.1.
  • Upgraded Atoti UI and Atoti Admin UI to 5.2.1.

Fixed

0.9.0

Released on September 13, 2024.

Added

  • atoti-directquery-jdbc to connect to an external database through JDBC.
  • 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:
    - 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:
    - 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.

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 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() for symmetry with atoti.Session.connect() (the removed section gives more details about that latter method):
    - session = tt.Session()
    + session = tt.Session.start()
    
    The top-level config parameters have been grouped into a SessionConfig dataclass providing better error reporting and allowing code reuse:
    - 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:
      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:
    - 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 to be installed.

DirectQuery

  • DirectQuery *ConnectionInfo and *TableOptions classes have been renamed ConnectionConfig and TableConfig.
    - from atoti_directquery_clickhouse import ClickhouseConnectionInfo
    + from atoti_directquery_clickhouse import ConnectionConfig
    
    - 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:
    - 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.

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, KmsConfig, and KeyPair.
  • The atoti-sql package has been renamed atoti-jdbc.
  • atoti.Session.explain_mdx_query() and atoti.Cube.explain_query() have been replaced with an explain parameter to atoti.Session.query_mdx() and atoti.Cube.query():
    - session.explain_mdx_query(mdx)
    + session.query_mdx(mdx, explain=True)
    
  • atoti.Table.keys returns a tuple instead of a list. It communicates that keys cannot be changed once the table exists.
  • create_cube()’s base_table parameter has been renamed fact_table.

User interface

  • Upgraded Atoti UI and Admin UI to 5.2.0.

Deprecated

  • atoti.Session.port. Use atoti.Session.url instead:
    - url = f"http://localhost:{session.port}"
    + url = session.url
    
    - port = session.port
    + from urllib.parse import urlparse
    + port = urlparse(session.url).port
    
  • atoti.Session.start_transaction(). Use atoti.tables.Tables.data_transaction() instead:
    - with session.start_transaction(): ...
    + with session.tables.data_model_transaction(): ...
    
  • atoti.Session.security.basic. Use basic_authentication:
    - 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:
    - column_names = table.columns
    + column_names = list(table)
    
  • atoti.Hierarchy.levels. Iterate on the Hierarchy instead:
    - level_names = list(h["Geography"].levels)
    + level_names = list(h["Geography"])
    
    - h["Date parts"] = {**h["Date parts"].levels, "Date": table["Date"]}
    + h["Date parts"] = {**h["Date parts"], "Date": table["Date"]}
    

Removed

  • Support for Java 17, 18, 19, and 20.
  • The atoti-query package and its QuerySession class. Use atoti.Session.connect() instead:
    - pip install atoti-query
    + pip install atoti-client
    
    - 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 to count rows and len(list(table)) to count columns:
    - row_count = len(table)
    + row_count = table.row_count
    

Previously deprecated