0.7.0 (August 16, 2022)#



User interface#

  • Ability to hide and show table columns.


  • Table is automatically partitioned for improved default performance.

User interface#

  • Upgraded Atoti UI to 5.0.15.

Functions and methods#

The following changes are BREAKING.

  • The signature of the atoti.agg functions have been clarified:

    • a scope cannot be passed when aggregating a Column (or a column operation) or a Level.

    • a scope must be passed otherwise.

  • The signature of functions creating measures in the atoti, atoti.array, and atoti.math modules and of the methods Session.read_*() and Table.load_*() have been changed to make more parameters positional-only.

  • read_csv() and load_csv()’s separator and process_quotes parameters default to "," and True (respectively). None can still be passed to force the inference of these arguments in a preliminary partial read of the CSV file.

  • read_csv() and load_csv() attempt to parse dates in the ISO 8601 format only. To parse dates in other formats, specify their pattern in the date_patterns argument.

  • date_shift() results are not impacted by conditions and filters when executing a query (same behavior as shift()).

  • atoti.scope.cumulative() with a time period window matches pandas and Excel’s behavior (issue #396).

    -   window=("-1D", None),
    +   window=("-2D", None)
  • delete_scenario()’s scenario parameter has been renamed name.


The following changes are BREAKING.

  • Different Table with Column with the same name can be joined (issue #655).

    • Saved drillthrough widgets must be recreated as their MDX query has changed to use both the table and column names to uniquely reference a field.

    • atoti_plus.security.Restrictions must specify both the table and column names using a tuple.

  • The DataType class has been replaced with a Literal type. The main data types still have constants in atoti.type. Nullable types have been removed, set the default_value to None instead for non-numeric types:

    - session.create_table("Example", types={"String": tt.type.NULLABLE_STRING})
    + session.create_table("Example", types={"String": tt.STRING}, default_values={"String": None})
  • Default values for temporal Column have been changed from "N/A" to actual temporal values. See default_value for an exhaustive list.

  • Passing a UserContentStorageConfig pointing to a local H2 database will not automatically migrate the database to H2 v2 format during session startup anymore. Downgrade to Atoti 0.6.6 and start a session configured to use this local H2 database to migrate it to v2 format to be able to use the database with Atoti 0.7.0.



The following removals are BREAKING.

  • Ability to create a Hierarchy on a nullable Column. Set the column’s default_value to something different than None before creating the Hierarchy instead.

  • Implicit required levels. Measures created from value(), Level, or conditions implicitly added a OriginScope on the corresponding level(s) in subsequent calls to atoti.agg functions. This undocumented behavior has been removed so these implicit scopes will have to be defined explicitly. For example:

      table = session.create_table(
              "Product": tt.STRING,
              "Date": tt.LOCAL_DATE,
              "Price": tt.INT,
          keys=["Product", "Date"],
      cube = session.create_cube(table)
      l, m = cube.levels, cube.measures
      price_column = table["Price"]
    - m["Price"] = tt.agg.sum(tt.value(price_column))
    + m["Price"] = tt.agg.sum(
    +     tt.agg.single_value(price_column),
    +     # Levels corresponding to the key columns of `price_column`'s table.
    +     scope=tt.OriginScope(l["Product"], l["Date"]),
    + )

    Note: The example above illustrates the required changes but is contrived: tt.agg.sum(price_column) would be better in both cases.

    Set the ATOTI_REQUIRED_LEVELS_WARNING environment variable to "True" to be warned of the creation of measures that used to have required levels.

  • value()’s levels parameter. Use a combination of atoti.agg.single_value() and where() instead:

    - m["City.VALUE"] = tt.value(table["City"], levels=[l["Country"]])
    + m["City.VALUE"] = tt.where(
    +   l["Country"] != None,
    +   tt.agg.single_value(table["City"]),
    + )
  • Parameter hierarchized_columns of table creation methods (create_table(), read_pandas(), read_spark(), read_csv(), read_parquet(), read_numpy() and read_sql()). Create hierarchies in batch with atoti.hierarchies.Hierarchies.update instead:

      table = session.create_table(
          types={"Date": tt.LOCAL_DATE, "Product": tt.STRING, "Quantity": tt.DOUBLE},
    -     hierarchized_columns=["Date", "Product"],
      cube = session.create_cube(table, mode="manual")
    + cube.hierarchies.update({name: {name: table[name]} for name in ["Date", "Product"]})
  • atoti.__version__. Use importlib.metadata’s version() like for any other library instead:

      import atoti as tt
    + from importlib.metadata import version
    - atoti_version = tt.__version__
    + atoti_version = version(tt.__name__)
  • date_shift()’s offset and atoti.scope.cumulative()’s window support of quarter units. Use "3M" instead of "1Q".

  • Support for remote content servers. Configure the user content storage with a JDBC url instead.

  • The custom Exception classes have been made private.

  • atoti.Table.loading_report has been made private.

Previously deprecated#

  • create_session() and open_query_session(). Instantiate Session and QuerySession (respectively) instead.

  • QuerySession’s name attribute.

  • atoti.query module. Instead, import functions and classes from atoti if installed or from atoti-query otherwise.

  • atoti.query.create_basic_authentication and atoti.query.create_token_authentication. Use BasicAuthentication and TokenAuthentication instead.

  • atoti.level.Level.comparator.

    • instead of atoti.comparator.ASCENDING and atoti.comparator.DESCENDING, use NaturalOrder.

    • instead of atoti.comparator.first_members(), use CustomOrder.

  • Passing timeouts as instances of int. Use datetime.timedelta instead.

  • create_parameter_simulation() measure_name and default_value parameters. Use the measures parameter instead.

  • Context value keys queriesResultLimit.intermediateSize and queriesResultLimit.transientSize in shared_context, query(), and query_mdx(). Use queriesResultLimit.intermediateLimit and queriesResultLimit.transientLimit (respectively) instead.

  • atoti_kafka.create_deserializer() and load_kafka()’s deserializer parameter. Only supported records are JSON objects with keys matching Table’s columns.


  • atoti.config.session_config.SessionConfig.aws and atoti.config.session_config.SessionConfig.azure. Use read_csv(), load_csv(), read_parquet(), or load_parquet()’s client_side_encryption parameter instead.

  • atoti_aws.create_aws_key_pair() and atoti_aws.create_aws_kms_config(). Instantiate AwsKeyPair and AwsKmsConfig (respectively) instead.

  • atoti_azure.create_azure_key_pair(). Instantiate AzureKeyPair instead.

  • Session’s certificate_authority parameter. Use HttpsConfig’s certificate_authority parameter instead.

  • atoti.LdapConfig.role_mapping and atoti.OidcConfig.role_mapping. Use atoti_plus.security.LdapSecurity.role_mapping and atoti_plus.security.OidcSecurity.role_mapping (respectively) instead.

  • atoti.LoggingConfig.file_path. Use atoti.LoggingConfig.destination instead.