0.8.0 (June 02, 2023)#
Added#
atoti-directquery-mssql
plugin for Microsoft SQL Server.Support for exclusion filter in
atoti.Cube.query()
andatoti_query.QueryCube.query
(issue #742).atoti.parent_value()
’s dense parameter.
Changed#
The
atoti-plus
plugin has been merged with the mainatoti
package. The DirectQuery plugins can be installed without configuring a private PyPI/Conda repository. The DirectQuery plugins and the features that were part of theatoti-plus
plugin still require a license key to be used. See how to Unlock all features.atoti_plus.ADVANCED_APP_EXTENSION
has moved toatoti.app_extension.ADVANCED_APP_EXTENSION
and has changed from atuple
to aMapping
for simpler use.atoti_plus.UserServiceClient
has moved back toatoti.Session.security
.Graphviz is not declared as a dependency of the
atoti
Conda package for consistency with the Python wheel package. Install it before usingatoti.tables.Tables.schema
oratoti.Cube.schema
.The
atoti-jupyterlab
plugin has been renamedatoti-jupyterlab3
. It is compatible with JupyterLab 3. This will allowatoti-jupyterlab
to target JupyterLab 4 in a future non-breaking Atoti release.The dtypes of the pandas DataFrames returned by
atoti.Table.head()
,atoti.Cube.query()
,atoti.Session.query_mdx()
,atoti_query.QueryCube.query
, andatoti_query.QuerySession.query_mdx
match theDataType
of the correspondingColumn
,Level
, andMeasure
. Here are some examples:Int64
for aColumn
with along
data_type
and aNone
default_value
.float32
for aColumn
with afloat
data_type
and a non-None
default_value
.int32
for aLevel
with anint
data_type
. The dtype will become nullable (i.e.Int32
) ifatoti.Cube.query()
’s include_totals oratoti.Session.query_mdx()
’s keep_totals are set toTrue
.
The attributes of
AggregateProvider
have been made immutable. They have also been made private because some of them are instances of classes that are, for now, private.
User interface#
Upgraded the app to Atoti UI 5.1.4. The dashboards and widgets saved in previous versions of Atoti need to be migrated. Follow this guide with the following modifications:
Make sure to connect as a user with access to all the content that must be migrated. We recommend connecting as a user with the role ROLE_CS_ROOT.
No roles are required when using Atoti Community Edition.
Instead of ROLE_CS_ROOT, the ROLE_ADMIN role is required when migrating a secured
Session
. To not have to deal with roles and security, the migration can be performed from a local session with user_content_storage set to the one to migrate and authentication set toNone
.
Create a file in the migration folder and name it
servers.json
To create that file, run the following code, adapting the logic to create a
atoti_query.QuerySession
targetting the session holding the user content storage to migrate:import json from pathlib import Path import atoti as tt # Replace the URL below with the one of the session to migrate. query_session = tt.QuerySession("http://localhost:56035") data_model = query_session._fetch_discovery() servers_json = json.dumps( {"default": {"url": query_session.url, "dataModel": data_model}}, indent=2, ) Path("servers.json").write_text(servers_json)
Deprecated#
Support for pandas 1. Update to pandas >= 2.0.0 instead.
UserServiceClient.basic.create_user()
. Useatoti_query.security.basic_security.BasicSecurity.credentials
instead:- from atoti_plus import UserServiceClient - user_service_client = UserServiceClient.from_session(session) - user_service_client.basic.create_user( - "Jean", password="mot de passe", roles=["ROLE_FRANCE"] - ) - user_service_client.basic.create_user( - "John", password="password", roles=["ROLE_UK"] - ) + session.security.basic.credentials.update({ + "Jean": "mot de passe", + "John": "password", + }) + session.security.individual_roles.update({ + "Jean": {"ROLE_FRANCE", "ROLE_USER"}, + "John": {"ROLE_UK", "ROLE_USER"}, + })
UserServiceClient.create_role
andUserServiceClient.roles
. Useatoti_query.security.Security.restrictions
instead:- from atoti_plus import UserServiceClient - user_service_client = UserServiceClient.from_session(session) table = session.create_table( "Example", types={"Country": tt.STRING, "Language": tt.STRING} ) - user_service_client.create_role( - "ROLE_FRANCE", - restrictions={ - (table.name, "Country"): {"France"}, - (table.name, "Language"): {"French"}, - }, - ) - user_service_client.create_role( - "ROLE_USA", - restrictions={ - (table.name, "Country"): {"USA"}, - (table.name, "Language"): {"English", "Spanish"}, - }, - ) + session.security.restrictions.update( + { + "ROLE_FRANCE": (table["Country"] == "France") + & (table["Language"] == "French"), + "ROLE_USA": (table["Country"] == "USA") + & (table["Language"].isin("English", "Spanish")), + } + )
Ability to mutate individual and mapped roles. Reassign them instead:
- from atoti_plus import UserServiceClient - user_service_client = UserServiceClient.from_session(session) - user_service_client.individual_roles["John"].update({"ROLE_A", "ROLE_B"}) + session.security.individual_roles["John"] |= {"ROLE_A", "ROLE_B"} - user_service_client.individual_roles["John"].remove("ROLE_A") - user_service_client.individual_roles["John"].remove("ROLE_B") + session.security.individual_roles["John"] -= {"ROLE_A", "ROLE_B"}
Removed#
Support for Python 3.8.
Support for
ACTIVEPIVOT_LICENSE
environment variable. UseATOTI_LICENSE
instead.Support for setting a
default_value
of a different type than the column’sdata_type
.session.create_table( "Example", types={"Date": "LocalDate"}, - default_values={"Date": "N/A"}, + default_values={"Date": None} )
Previously deprecated#
Comparing a
Column
,Level
, orMeasure
againstNone
. Use the correspondingisnull()
method instead.Passing a
Level
toshift()
’s on parameter. Pass aHierarchy
instead.atoti.value()
. Useatoti.agg.single_value()
instead.Passing a
Mapping
toat()
anddrop()
’s coordinates parameter, and tojoin()
. Pass aCondition
instead.atoti.Cube.query()
,atoti_query.QueryCube.query()
andatoti.Cube.explain_query()
’s condition parameter. Use the filter parameter instead.Scope factory functions
atoti.scope.cumulative()
,atoti.scope.origin()
, andatoti.scope.siblings()
. Instantiate their corresponding class:CumulativeScope
,OriginScope
, andSiblingsScope
instead.atoti.hide_new_license_agreement_message()
. Useatoti.hide_new_eula_message()
instead.Automatic assignment of the ROLE_USER role when using
atoti_plus.BasicSecurity.create_user()
. Assign the role manually through theatoti_query.security.Security.individual_roles
instead.UserServiceClient.kerberos.create_user()
. It was unnecessary as users authenticating through Kerberos will automatically be able to access the application if they are granted the ROLE_USER role throughatoti_query.security.kerberos_security.KerberosSecurity.default_roles
oratoti_query.security.Security.individual_roles
:- user_service_client.kerberos.create_user("Jean", roles={"ROLE_FRANCE"}) + user_service_client.individual_roles["Jean"] = {"ROLE_FRANCE", "ROLE_USER"}